Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Ruby

Làm chủ cú pháp `&:`: Đơn giản hóa `items.map(&:name)` trong Ruby

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. :)