24/01/2019, 15:22

Toán tử & trong ruby

Bài đăng này sẽ tập trung vào cách sử dụng của toán tử &. & có thể khá khó hiểu vì nó có ý nghĩa khác nhau tùy thuộc vào bối cảnh mà nó được sử dụng. Trong thực tế, cả hai phép toán unary (& object) và binary (object & object) đều có ý nghĩa trong Ruby. Để hiểu những điều này, chúng ...

Bài đăng này sẽ tập trung vào cách sử dụng của toán tử &. & có thể khá khó hiểu vì nó có ý nghĩa khác nhau tùy thuộc vào bối cảnh mà nó được sử dụng. Trong thực tế, cả hai phép toán unary (& object) và binary (object & object) đều có ý nghĩa trong Ruby. Để hiểu những điều này, chúng ta hãy cùng xét 1 số ví duh cụ thể ở dưới đây.

The Binary &

Bitwise AND

Bitwise AND là bit-by-bit nhị phân tương đương với phép AND nhị phân. 101 & 100 = 100 và 101 & 001 = 1 Toán tử & định nghĩa phép AND cho Bignum, Fixnum và Process::Status và chỉ chuyển đổi số nguyên sang nhị phân thực hiện phép AND bit. Process::Status chuyển đổi trạng thái số sang Fixnum và sử dụng nó để thực hiện thao tác

irb(main):001:0> 14 & 13
=> 12

Kết quả của phép toán treen có thể được nhìn thấy rõ ràng bằng cách chuyển đổi chúng thành nhị phân

irb(main):001:0> "#{14.to_s(2)} & #{13.to_s(2)} = #{12.to_s(2)}"
=> "1110 & 1101 = 1100"

Giao 2 tập hợp

Có thể việc sử dụng đơn giản nhất của toán tử & là trong lớp Array. & là toán tử giao 2 tập hợp, có nghĩa là kết quả là tập hợp các phần tử đều tồn tại trong cả hai mảng.

irb(main):001:0> [1,2,3] & [1,2,5,6]
=> [1, 2]

Boolean AND

Trên FalseClass, NilClass và TrueClass, toán tử và đại diện cho phép AND. Chú ý là nó không hoạt động như toán tử &&, vì nó chỉ được định nghĩa trên ba lớp này.

irb(main):001:0> false & true
=> false
irb(main):002:0> nil & true
=> false
irb(main):003:0> true & Object.new
=> true
irb(main):004:0> Object.new & true
=> NoMethodError: undefined method &' for #<Object:0x007f9e7ac96420>

The Unary &

Bây giờ thì phức tạp hơn rồi. Nó gần như tương đương với việc gọi #to_proc trên object, nhưng không hoàn toàn. Ruby có 2 kiểu của blocks code đó là Blocks và Procs và có 1 số khác biệt quan trọng. Bạn có thể định nghĩa và tham chiếu Procs và gán chúng cho các biến. Blocks luôn liên quan đến một cuộc gọi phương thức và có thể được định nghĩa bên ngoài bối cảnh đó. Cách bạn phân biệt chúng là Procs luôn đi trước Proc.new, Proc, lambda hoặc -> () khi chúng được xác định. &object sẽ có ý nghĩa như sau:

  • Nếu object là block, nó sẽ convert sang proc.
  • Nếu object là proc, nó sẽ convert sang lambda trạng thái của object đó
  • Nếu object ko là 1 proc, đầu tiên nó gọi #to_proc trên đối tượng và sau đó chuyển đổi nó thành một khối. Cùng xem 1 số ví dụ

Nếu object là một block, nó chuyển đổi khối thành một Proc đơn giản. Ví dụ đơn giản nhất về điều này là khi chúng ta muốn có quyền truy cập vào khối mà chúng ta truyền cho một phương thức, thay vì chỉ gọi yield. Để làm điều này, chúng ta cần chuyển đổi khối thành một Proc.

def describe(&block)
  "The block that was passed has parameters: #{block.parameters}"
end
irb(main):001:0> describe{ |a,b| }
=> "The block that was passed has parameters: [[:opt, :a], [:opt, :b]]"
irb(main):002:0> describe do |*args|
irb(main):003:0> end
=> "The block that was passed has parameters: [[:rest, :args]]"

Nếu object là proc, nó sẽ convert sang lambda trạng thái của object đó Đây là một trường hợp cực kỳ hữu ích của toán tử &. Chẳng hạn, chúng ta biết rằng Array#map tạo một block, nhưng nếu chúng ta muốn sử dụng lại nhiều lần.

irb(main):001:0> multiply = lambda{ |x| x*2 }

irb(main):002:0> [1,2,3].map(&multiply)
=> [2, 4, 6]
irb(main):003:0> [4,5,6].map(&multiply)
=> [8, 10, 12]
def describe(&block)
  "Calling lambda? on the block results in #{block.lambda?}."
end
irb(main):001:0> describe(&lambda{})
=> "Calling lambda? on the block results in true."
irb(main):002:0> describe(&proc{})
=> "Calling lambda? on the block results in false."
class Container
  define_method(:m, &proc{})
end
irb(main):001:0> describe(&Container.new.method(:m).to_proc)
=> "Calling lambda? on the block results in true."

Nếu object ko là 1 proc, đầu tiên nó gọi #to_proc trên đối tượng và sau đó chuyển đổi nó thành một block Trường hợp phổ biến nhất của điều này có lẽ là gọi Array#map với symbol

irb(main):001:0> ["1", "2", "3"].map(&:to_i)
=> [1, 2, 3]

Nó hoạt động bởi vì gọi Symbol#to_proc. :to_i trước tiên được chuyển thành 1 proc, sau đó thành 1 block. Chúng ta có thể tự tạo các phương thức to_proc

class Display
  def self.to_proc
    lambda{ |x| puts(x) }
  end
end

class FancyDisplay
  def self.to_proc
    lambda{ |x| puts("** #{x} **") }
  end
end
irb(main):001:0> greetings = ["Hi", "Hello", "Welcome"]

irb(main):002:0> greetings.map(&Display)
Hi
Hello
Welcome
=> [nil, nil, nil]

irb(main):003:0> greetings.map(&FancyDisplay)
** Hi **
** Hello **
** Welcome **
=> [nil, nil, nil]

Hy vọng bài viết này sẽ giúp ích cho mọi người. Cảm ơn vì đã theo dõi bài viết của mình.

0