07/09/2018, 18:17

100 "sự thật" thú vị về Ruby (Part 1)

Sau 2 năm làm việc với ruby, quả thật ngôn ngữ này còn chứa nhiều thứ hay ho mà bản thân mình chưa biết. Trong quá trình tìm hiểu, mò mẫm, mình có sưu tầm dược một bài viết khá hay. Mình xin phép được giới thiệu lại với mọi người. 100 "sự thật" nhỏ nhỏ mà đầy thứ vị. Method methods Bởi vì ...

Sau 2 năm làm việc với ruby, quả thật ngôn ngữ này còn chứa nhiều thứ hay ho mà bản thân mình chưa biết. Trong quá trình tìm hiểu, mò mẫm, mình có sưu tầm dược một bài viết khá hay. Mình xin phép được giới thiệu lại với mọi người. 100 "sự thật" nhỏ nhỏ mà đầy thứ vị.

  1. Method methods

Bởi vì hầu hết tất cả mọi thứ trong Ruby đều là Object vậy nên bạn có thể dùng .methods để xem tất cả các method của Object

4.methods - Object.methods
# => [:[email protected], :+, :-, :*, :/, :div, :%, :modulo, :divmod, :fdiv, :**, :abs, :magnitude, :~, :&, :|, :^, :[], :<<, :>>, :to_f, :size, :bit_length, :zero?, :odd?, :even?, :succ, :integer?, :upto, :downto, :times, :next, :pred, :chr, :ord, :to_i, :to_int, :floor, :ceil, :truncate, :round, :gcd, :lcm, :gcdlcm, :numerator, :denominator, :to_r, :rationalize, :singleton_method_added, :coerce, :i, :[email protected], :remainder, :real?, :nonzero?, :step, :quo, :to_c, :real, :imaginary, :imag, :abs2, :arg, :angle, :phase, :rectangular, :rect, :polar, :conjugate, :conj, :between?]
  1. Biến _

Trong IRB nếu ta sử dụng biến _ sẽ giữ giá trị output của code mà đôi khi giá trị của nó ta không dùng tới hoặc ví dụ đơn giản chưa biết đặt tên như nào:

numbers = [4, 1, 2, 7]
a, _, b, c = numbers
puts "a=#{a}, b=#{b}, c=#{c}" #=> a=4, b=2, c=7

Một ví dụ khác là biến _ giúp ta bỏ qua giá trị phần tử tại vị trí được asign tới nó:

arr = [["John", "Doe", 15], ["Jane", "Doe", 28]]
arr.reduce("") do |acc, (name, _, age)|
  acc << "#{name} (#{age}) "
end #=> Doe (15) Doe (28)
  1. instance_exec

instance_exec method có thể dùng với mọi Object, à mà mọi thứ trong Ruby đều là Object mà :v Các sử dụng tương tự với làm việc với singleton_class (defind method cho duy nhất một object). Nghe có vẻ giống tạo instance method cho class, nhưng đơn giản hơn ko phải viết define class mà khai báo luôn và ngay như sau:

num = Object.new
num.instance_exec {
  def == other
    other == 3
  end
}
num == 4
# => false
num == 3
# => true
  1. Enumerator::Lazy

Một Enumerator::Lazy object sẽ trả lại cho bạn một object tại thời điểm mà đoạn code thực thi tới từng item trong nó.

def do_the_lazy(array_input)
  Enumerator::Lazy.new(array_input) do |yielder, value|
    yielder << value
  end
end

x = do_the_lazy([1,2,3,4,5,6])
# => #<Enumerator::Lazy: [1, 2, 3, 4, 5, 6]:each>

x.next
# => 1
x.next
# => 2
x.next
# => 3
x.force
# => [1, 2, 3, 4, 5, 6]
  1. Struct kế thừa từ Enumerable

Bởi vì Struct kế thừa từ Enumerable vậy nên bạn có thể sử dụng bất kì method nào của Enumrable.

class Pair < Struct.new(:first, :second)
  def same?
    inject(:==)
  end

  def add
    reduce(:+)
  end
end

a = Pair.new(4,4)
# => #<struct Pair first=4, second=4>
a.same?
# => true
a.add
# => 8

b = Pair.new(5,6)
# => #<struct Pair first=5, second=6>
b.same?
# => false
b.add
# => 11
  1. $:

Biến $: là đường dẫn cho Ruby gem, hoặc Ruby có định nghĩa sẵn có thể dùng $$OAD_PATH. Bạn có thể add đường dẫn thư mục gem cần load tới load path bằng cú pháp:

$: << '.'
  1. inspect

The inspect method là default method của Object khi bạn gọi Class#new.

class Human < Struct.new(:name, :age)
  def inspect
    "#{name} is a human of age #{age}"
  end
end

joe = Human.new("Joe", 43)
# => Joe is a human of age 43
  1. Hash#invert

Đảo ngược cặp Hash key-value:

{a: 1, b: 2}.invert
# => {1=>:a, 2=>:b}
  1. Method#to_proc

Bạn có thể convert method sang proc

def plus_one(x)
  x + 1
end

proc_increment = method(:plus_one).to_proc

proc_increment.call(4)
# => 5
[1,3,5,9].map(&proc_increment)
# => [2, 4, 6, 10]
  1. module_function

module_function là một module private với class. Được gọi tới bằng include.

module Mod
  def one
    "This is one"
  end
  module_function :one
end
class Cls
  include Mod
  def call_one
    one
  end
end
Mod.one     #=> "This is one"
c = Cls.new
c.call_one  #=> "This is one"
module Mod
  def one
    "This is the new one"
  end
end
Mod.one     #=> "This is one"
c.call_one  #=> "This is the new one"
  1. require_relative

require_relative là cách tiện dụng để load các file ruby khác để đặt vào location của file hiện tại.

  1. instance_methods

Với mỗi class bạn có thể gọi .instance_methods để tìm tất cả các instance method mà được định nghĩa của class.

  1. Enumerable

Enumerable là một module bao gồm các định nghĩa về định dạng cơ bản như Array, Hash, Struct. Vì vậy tất cả những tất các các định dạng đó đều có các instance method từ Enumrable.

  1. defined?

defined? method giúp bạn kiểm tra bất kể thứ gì là module, class, method..... Với class hay module bạn còn có thể kiểm tra method_defined?, public_method_defined?, private_method_defined?, and protected_method_defined?

  1. –noprompt

Là 1 option nhỏ khi bạn viết code trong IRB (console), dòng lệnh với flag -noprompt sẽ tạo IRB session, không có kí tự bên trái terminal. Dễ dàng hơn cho việc kiểm tra code cũng như copy & paste code chứ?

  1. string % value(s)

Bạn có thể insert định dạng vào string bằng cách dùng method %

"Number: %f %f" % [1,2]
# => Number: 1.000000 2.000000

"Number: %e" % "6"
# => "Number: 6.000000e+00"
  1. ternary operator _ ? _ : _

Toán tử Ternary thì khỏi cần nói tới nữa rồi nhỉ?

true ? 10 : 20
# => 10 
false ? 10 : 20
# => 20 
false ? 10 : 20 ? 30 : 40
# => 30
false ? 10 : !20 ? 30 : 40
# => 40
false ? 10 : 20 ? 30 ? 50 : 60 : 40
# => 50
false ? 10 : 20 ? !30 ? 50 : 60 : 40
# => 60
false ? 10 : !20 ? 30 ? 50 : 60 : 40
# => 40
  1. ruby -e “#code”

Ta có thể chạy code ruby ngay trong command line

home:~$ ruby -e "puts 1 + 1"
2
  1. %[]

%[] cho phép bạn sử dụng ngoặc đơn, ngoặc kép cùng cả nhúng #{} vào string

%[Hello #{ "World!" } "Quoted" and 'quoted!']
# => "Hello World! "Quoted" and 'quoted!'"
  1. erb

ERB là thư viện quá quen thuộc được tích hợp Ruby, bạn có thể sử dụng để sử dụng để thực thi code ruby:

require "erb"
ERB.new(%[<html><body>Hello <%= "Wor" + "ld!" %></body></html>]).result
# => "<html><body>Hello World!</body></html>"
  1. undefined Class instance variables không báo lỗi.

Khi một biến chưa được định nghĩa và bạn sử dụng class instance variable, thực ra tên gọi cao siêu mà nó đơn giản là cái biến @ đấy, Ruby sẽ trả về nil chứ ko phải “undefined” errors.

@a
# => nil
@a.to_i
# => 0

class A
end

A.new.instance_eval {@a}
# => nil

def count_from_one()
  @num = @num.to_i + 1
end
count_from_one
# => 1 
count_from_one
# => 2 
count_from_one
# => 3 
count_from_one
# => 4
  1. UnboundMethod

Bạn có thể trích xuất một instance method của một class và sử dụng như một Proc độc lập với bind.

split = String.instance_method(:split)
# => #<UnboundMethod: String#split>

class String
  undef :split
end

"asdf".split("s")
#NoMethodError: undefined method `split' for "asdf":String

split.bind("asdf").call("s")
# => ["a", "df"]
  1. ObjectSpace

Bạn có thể truy xuất bất cứ thứ gì được cấp phát trong bộ nhớ thông qua ObjectSpace

class A
end

3.times do
  A.new
end

ObjectSpace.each_object(A).count
# => 3
ObjectSpace.each_object(A).to_a
# => [#<A:0x0000000204cc00>, #<A:0x00000002244800>, #<A:0x00000002254430>]
  1. freeze

Có một mẹo đơn giản để khoá một object, như tên gọi của hàm vậy, freeze một object sẽ không thể chỉnh sửa được.

module Test
  def self.example
    "Hello World!"
  end
end

Test.freeze
Test.example
# => "Hello World!" 

module Test
  def self.asdf
    123
  end
end
#RuntimeError: can't modify frozen Module
  1. self có thể dùng để thay thế object name
module Apple
  def Apple.chew
    "munch munch"
  end
end

Apple.chew
# => "munch munch" 

def Apple.cut
  "chop chop"
end

Apple.cut
# => "chop chop"

class A
  def A.foo
    "bar"
  end
end

A.foo
# => "bar"
  1. Bạn có thể truy cập scope có level cao nhất với ::
module A
  def self.test
    "FOO"
  end
end

module Thing
  module A
    def self.test
      "BAR"
    end
  end

  def Thing.inner
    A.test
  end

  def Thing.outer
    ::A.test
  end
end

Thing.outer
# => "FOO"
Thing.inner
# => "BAR"
  1. prepend

prepend thêm một module vào chuỗi class mà nó kế thừa. Từ đó ta có thể gọi các methods ra. Lưu ý đặt tên method thật cẩn thận để tránh gây sai xót không đáng có nhé.

module A
  def split
    self.upcase
  end
end

String.prepend A

String.ancestors
# => [A, String, Comparable, Object, Kernel, BasicObject] 
"asdf".split
# => "ASDF"
  1. super

super hiểu đơn giản là method gọi tới parent class method.

module A
  def split(*_)
    super("a")
  end
end

class B < String
  def split
    super("b")
  end
end

b = B.new("123abc")
# => "123abc" 
b.split
# => ["123a", "c"]
B.ancestors
# => [B, String, Comparable, Object, Kernel, BasicObject]

String.prepend A

b.split
# => ["123", "bc"] 
B.ancestors
# => [B, A, String, Comparable, Object, Kernel, BasicObject]
  1. arity

arity giúp bạn biết được có bao nhiêu parameter mà một method cần.

->{}.arity
# => 0 
->_{}.arity
# => 1 
->_,_{}.arity
# => 2 
->*_{}.arity
# => -1
"".method(:upcase).arity
# => 0 
String.instance_method(:upcase).arity
# => 0
  1. cloning Arrays

Khi bạn dùng method Array#clone bạn có được một array khác giống hệt các phần tử chưa trong array cũ. Tuy nhiên sẽ không có thêm phần bộ nhớ nào được cấp phát cho các phẩn tử trong array mới, chúng chỉ khác nhau ở con trỏ trỏ tới các phần tử (mình nói nôm na định nghĩa bên C/C++ là thế :v). Vì vậy clone sẽ hữu hiệu khi bạn xử lý cần tính toán tiết kiệm bộ nhớ. Array#dup cũng là một method tương tự.

class Thing
end
a = [Thing.new, Thing.new]
# => [#<Thing:0x000000017eba48>, #<Thing:0x000000017eba20>] 
b = a.clone
# => [#<Thing:0x000000017eba48>, #<Thing:0x000000017eba20>] 
a.object_id
# => 12541180 
b.object_id
# => 12522640 
a.map(&:object_id)
# => [12541220, 12541200] 
b.map(&:object_id)
# => [12541220, 12541200]

Nếu bạn chỉnh sửa Array bạn không cần lo về array được clone bị ảnh hưởng, tuy nhiên nếu bạn chỉnh sửa một phần tử trong array thì cả 2 phần tử trong 2 array sẽ thay đổi.

  1. Gía trị mặc định của Hash

Như ví dụ trên về duplicate một array nhưng lại tạo ra array mà chung các phần tử trong bộ nhớ, chúng ta cũng có cùng các phần tử được trả về khi ta set giá trị mặc định cho Hash.

h = Hash.new
# => {} 
h.default = []
# => [] 
a = h[:c]
# => [] 
b = h[:d]
# => [] 
a[0] = 1
# => 1 
h[:z]
# => [1]

Để khắc phục điều trên, tốt hơn ta nên dùng default_proc để tạo new Array.

h = Hash.new
# => {} 
h.default_proc = ->hsh,key{ hsh[key] = [] }
# => #<Proc:[email protected](irb):8 (lambda)> 
a = h[:c]
# => [] 
b = h[:d]
# => [] 
a[0] = 1
# => 1 
h[:z]
# => []
  1. class_eval cùng với included Sử dụng class_eval khi viết thêm method include vào class là một cách tự nhiên để update class.
class A
end

module Example
  def self.included(base)
    base.class_eval do
      def example
        "instance method"
      end

      def self.example
        "class method"
      end
    end
  end
end

A.include Example

A.example
"class method"

A.new.example
"instance method"
  1. inherited

Khi bạn có một class kế thừa 1 class khác như kiểu "mixin" bạn có thể sử dụng như sau:

class Foo
  def self.inherited(base)
    base.class_eval do
      def bar
        "Drinking at the bar!"
      end
    end
  end

  def foo
    "bar"
  end
end

class A < Foo
end

A.new.foo
# => "bar"
A.new.bar
# => "Drinking at the bar!"

Foo.new.foo
# => "bar"
Foo.new.bar
#NoMethodError: undefined method `bar' for #<Foo:0x00000001916378>
  1. String#chars Split từng kí tự trong string
"asdf".chars
# => ["a", "s", "d", "f"]
  1. break :value

Tương tự như các ngôn ngữ khác, break phá vòng lặp và bạn có thể return giá trị tại vòng lặp đó.

x = loop do
  break 9
end

x
# => 9
  1. Toán tử &. độc lập Thực ra cách dùng của nó chính là tương tự như try. thôi, bạn có thể dùng nó thay thế cho đơn giản dễ viết hơn.
@a&.size
# => nil

@a = [1,2,3]

@a&.size
# => 3
  1. Hash#to_proc
hsh = {a: 1, b: 2, c: 3}

[:a, :b, :c].map(&hsh)
# => [1, 2, 3]

Bạn có thể đặt một dấu & trước object như là một parameter, nó được gọi là method to_proc.

  1. retry Trong bất kì một block begin/rescue/end, bạn có thể dùng retry để lặp lại đoạn code thực thi mỗi khi error được raise lên.
begin
  @x = @x.to_i + 1
  raise "Error" if @x < 5
rescue
  puts "We're rescuing!"
  retry
end
We're rescuing!
We're rescuing!
We're rescuing!
We're rescuing!
# => nil
  1. raise raise có thể có 3 tham số, text, error class, where error from.
raise "Hello World!"
#RuntimeError: Hello World!

raise StandardError, "Hello World!"
#StandardError: Hello World!

class DigestionError < StandardError
end

raise DigestionError, "stomach hurts", "bad food"
#DigestionError: stomach hurts
#	from bad food
  1. FILE Tên file hiện tại
# dir2/test.rb
puts __FILE__
test.rb
/full/path/to/dir2/test.rb
  1. LINE Dòng hiện tại
class LineNumber
  def self.before lineno, param
    puts "line number is #{lineno}"
  end

  def self.after param, lineno
    puts "line number is #{lineno}"
  end
end

print "before with __LINE__: "
LineNumber.before __LINE__, <<TEXT
This is some text.
As is this.
This, also, is some text.
TEXT

print "after with __LINE__: "
LineNumber.after <<TEXT, __LINE__
This is some text.
As is this.
This, also, is some text.
TEXT

print "after with __LINE__ + 0: "
LineNumber.after <<TEXT, __LINE__ + 0
This is some text.
As is this.
This, also, is some text.
TEXT
# fin

Output:

before with __LINE__: line number is 14
after with __LINE__: line number is 25
after with __LINE__ + 0: line number is 28
  1. Hash.[]
Hash[:array, :of, :key, :value, :pairs, "."]
# => {:array=>:of, :key=>:value, :pairs=>"."}
  1. Biến toàn cục

$ định nghĩa biến toàn cục, tuy nhiên bạn không nên sử dụng chúng. Hằng số có thể sử dụng thay thế, bạn cũng có thể định nghĩa biến toàn cục bằng các sử dụng :: trước tên biến.

module Kludge
  def Kludge.ugly
    $marco = :polo
  end
end

$marco
# => nil

Kludge.ugly
$marco
# => :polo 

module Nice
  def self.thing
    ::Marco = :polo
  end
end

Marco
#NameError: uninitialized constant Marco

Nice.thing
Marco
# => :polo
  1. $0

$0 là file gốc được thực thi trong ruby. Giống như python, name == “main” để chỉ chạy code trong file có tên là "main", tránh rung code từ các file ko cần thiết.

if __FILE__ == $0
  puts "You ran this #{__FILE__} directly! :-)"
end
  1. case permits then
case 1
when 1
then 
  puts "yes"
end
#yes
# => nil
  1. case không cần value
y = 4

case
  when y == 4 then puts "yes"
end
#yes
# => nil
  1. case then là tuỳ chọn
case 4
when 4
  puts "yes"
end
#yes
# => nil
  1. case === method
module Truth
  def self.===(thing)
    puts "=== has been called!"
    true
  end
end

case :pizza_is_delicious
when Truth
  puts "yummy food for my tummy"
end
#=== has been called!
#yummy food for my tummy
# => nil
  1. tail conditions

Bạn có thể đặt if recuse sau code nhé. Đôi lúc sẽ giúp code trông thoáng và dễ hiểu hơn nhiều.

@x = begin
  5
end if false

@x
# => nil

raise "Error" rescue :all_clear
# => :all_clear

if true
  puts "Hello World!"
end if false
# => nil
  1. sử dụng return

Ruby sẽ tự động trả về object cuối cùng, tuy nhiên return sẽ trả về giá trị và dừng thực thi code tại vị trí bạn muốn.

@x = 3

def a
  return true if @x == 3
  false
end

a
# => true

@x = 5
a
# => false
0