& trong ruby

Mở đầu:

Mình mới làm quen với ruby và đôi khi gặp kiểu viết như thế này:

'con ga nay'.split.map(&:length).reduce(&:*)
=> 18
user&.name.to_s
=>  ""

mình thấy dấu & rất hữu dụng trong ruby và bắt đầu tìm hiểu về nó, dưới đây là một số cách dùng phổ biến của &.

Bit AND (&)

& cho phép and bit trong ruby, hãy xem ví dụ sau

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

tương đương với

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

Interaction

& còn được dùng như phép giao nhau giữa các tập hợp

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

The And (&&)

true && false
=> false

chắc các bạn đều biết && chính là logic của AND rồi, và cách viết && ngắn hơn và có vẻ cool hơn :v hãy thử:

irb(main):056:0> a = true and false 
=> false
irb(main):057:0> a
=> true
irb(main):058:0> a = true && false
=> false
irb(main):059:0> a
=> false

bạn có nghĩ rằng && và AND giống nhau nữa không =)). Hãy xem ở đây bạn sẽ thấy rằng độ ưu tiên thực hiện của AND thấp hơn của &&. Như vậy trường hợp thứ 1 ở trên sẽ tương đương với cách viết sau:

irb(main):056:0> (a = true) and false 
=> false
irb(main):057:0> a
=> true

vậy tại sao vừa có && và AND tương tự như || và OR. Trong hầu hết trường hợp giá trị của chúng như nhau nhưng về ý nghĩa sử dụng AND và OR được coi là "control flow operators" (toán tử điều khiển luồng) còn && và || được coi là "logical operators" (toán tử logic). Một trong những trường hợp phổ biến dùng AND hơn && đó là control luồng xử lý data theo từng bước ví dụ

compile_code() AND assemble_binary() AND install_binary() 

Save navigation operator (&.)

Bạn có 1 table user_courses (id, user_id, course_id) chứa các bản ghi mà trong đó user học course tương ứng. Trong một vài tình huống

if user_course && user_course.user && user_course.user.address
.........
end

hoặc có thể viết

if user_course.try(:user).try(:address)
.........
end

hoặc là dùng &.

if user_course&.user&.address
.........
end

3 cách dùng này liệu có hoàn toàn như nhau hay không? hãy xem ví dụ dưới đây

irb(main):117:0* user_course = UserCourse.new(user: nil)
=> #<UserCourse id: nil, course_id: nil, user_id: nil>
irb(main):118:0> user_course.user.name
NoMethodError: undefined method `name' for nil:NilClass
irb(main):119:0> user_course && user_course.user && user_course.user.name
=> nil
irb(main):120:0> user_course.try(:user).try(:name)
=> nil
irb(main):121:0> user_course&.user&.name
=> nil

bây giờ hãy thử với

irb(main):122:0> user_course = UserCourse.new(user: false)
=> #<UserCourse id: nil, course_id: nil, user_id: nil>
irb(main):118:0> user_course.user.name
NoMethodError: undefined method `name' for nil:NilClass
irb(main):123:0> user_course && user_course.user && user_course.user.name
=> false
irb(main):124:0> user_course.try(:user).try(:name)
=> nil
irb(main):125:0> user_course&.user&.name
NoMethodError: undefined method `name' for false:FalseClass

như các bạn đã thấy ở trường hợp 2 này &&, try và &. trả về 3 giá trị hoàn toàn khác nhau. ở trường hợp của &&

irb(main):138:0> user_course.user
=> false

khi thực hiện user_course && user_course.user sẽ tương đương với nil && false khi thực hiện tới đây thì phép toán sẽ trả về false luôn và skip qua phần tử cuối cùng. Sự việc sẽ khác nếu bạn đổi thứ tự

irb(main):139:0>user_course.user.name && user_course && user_course.user
NoMethodError: undefined method `name' for false:FalseClass

trường hợp của try và &. như sau

irb(main):144:0> nil.try(:name)
=> nil
irb(main):146:0> false&.name
NoMethodError: undefined method `name' for false:FalseClass

như các bạn thấy save navigation operator (&.) có thể thực hiện với nil nhưng không thể thực hiện được với false, mặc dù nil và false đều là object. try() sẽ không quan tâm đến Object đó có tồn tại method được gọi hay không, &. sẽ tương đương với try!

user_course.try!(:user).try!(name)
NameError: undefined local variable or method `name' for main:Object

to_proc (&:symbol)

Blocks là một trong những đặc trưng khá hay của ruby hãy xem

'con ga nay'.split.map {|s| s.length}.reduce {|product, n| product * n }
=> 18

và với cách viết ngắn gọn hơn

'con ga nay'.split.map(&:length).reduce(&:*)

vậy ký tự & rốt cuộc đã làm cái gì vậy?

  • nếu object là Proc: & convert nó sang block
  • nếu object không phải là Proc: & call function to_proc của object, sau đó convert nó sang block. API của to_proc viết bằng C bạn có thể tham khảo tại đây. ý nghĩa của nó trên ruby có thể diễn đạt như sau
class Symbol
  def to_proc
    ->(obj, args = nil) { obj.send(self, *args) }
  end
end

hãy xem ví dụ dưới đây để hiểu về cách mà & đã convert symbol to block (names = 'con ga nay'.split)

names.map &:to_s

//  & sẽ gọi to_proc của chính object nên dòng trên sẽ thành
names.map &:to_s.to_proc

// thay thế to_proc bằng xử lý trong method
names.map &->(name, args = nil) { name.send(:to_s, *args) }

// map sẽ thực hiện method với từng phần tử
names.map &->(name) { name.send(:to_s) }

//  thay vì object.send(:xxx) ta thay bằng cách gọi trực tiếp method của object
names.map &->(name) { name.to_s }

// lambada ->{name.to_s} map với mỗi phần tử của names, ký tự & convert lambada thành proc. 
names.map { |name| name.to_s }

Tham khảo:

http://ablogaboutcode.com/2012/01/04/the-ampersand-operator-in-ruby http://mitrev.net/ruby/2015/11/13/the-operator-in-ruby/ https://www.tinfoilsecurity.com/blog/ruby-demystified-and-vs http://maximomussini.com/posts/ruby-to_proc/