Khi lặp qua mảng, có một cú pháp viết tắt mà tôi thường dùng: thủ thuật `&:`, còn gọi là "ampersand colon" hay "pretzel colon". Nếu chưa quen, đây là cách hoạt động:
words = ["would", "you", "like", "to", "play", "a", "game?"]
# này...
words.map &:length
# ..tương đương với:
words.map { |w| w.length }
Cho đến gần đây, tôi tưởng `&:` là một toán tử. Nhưng không, đó là một "mẹo" thông minh ban đầu trong ActiveSupport, trở thành tính năng chính thức từ Ruby 1.8.7.
Toán tử `&`
Ngoài dùng cho phép toán AND, ký tự `&` trong Ruby còn có vai trò khác. Khi đặt đầu đối số phương thức, nó gọi `to_proc` trên toán hạng và truyền vào như một block. Nghe hơi khó, dễ hiểu hơn qua ví dụ:
def my_method(&block)
block.call
end
class Greeter
def self.to_proc
Proc.new { "hello" }
end
end
my_method(&Greeter) # trả về "hello"
Symbol#to_proc
Bạn có thể thêm phương thức `to_proc` cho bất kỳ đối tượng nào, kể cả Symbol. Ruby làm chính điều này để cho phép tắt `&:`. Triển khai trông như sau:
class Symbol
def to_proc
Proc.new do |item|
item.send self
end
end
end
Phần quan trọng là `item.send(self)`. Ở đây `self` chính là symbol đó.
Tổng hợp lại
Các phương thức Enumerable như `each` và `map` nhận một block. Với mỗi phần tử, chúng gọi block và truyền tham chiếu đến phần tử. Block ở đây được sinh ra bằng cách gọi `to_proc` trên symbol.
# &:name evaluates to a Proc, which does item.send(:name)
items.map(&:name)
Thú vị là `map` không hề biết những gì đang xảy ra! Phần lớn công việc do symbol `:name` thực hiện. Đây chắc chắn là một thủ thuật thông minh... gần như "quá thông minh" theo gu tôi. Nhưng nó đã nằm trong thư viện chuẩn Ruby nhiều năm, và quá tiện lợi nên tôi chắc chắn sẽ tiếp tục dùng. :)