Tôi tìm thấy mã này trong RailsCast :
def tag_names
@tag_names || tags.map(&:name).join(' ')
end
Gì (&:name)
trong map(&:name)
bình?
Tôi tìm thấy mã này trong RailsCast :
def tag_names
@tag_names || tags.map(&:name).join(' ')
end
Gì (&:name)
trong map(&:name)
bình?
Câu trả lời:
Nó là viết tắt của tags.map(&:name.to_proc).join(' ')
Nếu foo
là một đối tượng với một to_proc
phương thức, thì bạn có thể truyền nó cho một phương thức &foo
, nó sẽ gọi foo.to_proc
và sử dụng nó làm khối của phương thức.
Các Symbol#to_proc
phương pháp ban đầu được bổ sung bởi ActiveSupport nhưng đã được tích hợp vào Ruby 1.8.7. Đây là thực hiện của nó:
class Symbol
def to_proc
Proc.new do |obj, *args|
obj.send self, *args
end
end
end
&
, tức làtags.map(&:name.to_proc).join(' ')
Một tốc ký tuyệt vời khác, nhiều người chưa biết, là
array.each(&method(:foo))
đó là một tốc ký cho
array.each { |element| foo(element) }
Bằng cách gọi, method(:foo)
chúng tôi đã lấy một Method
đối tượng từ self
đó đại diện cho foo
phương thức của nó và sử dụng &
để biểu thị rằng nó có một to_proc
phương thức chuyển đổi nó thành a Proc
.
Điều này rất hữu ích khi bạn muốn làm điều point-free style. Một ví dụ là kiểm tra xem có chuỗi nào trong một mảng bằng chuỗi không "foo"
. Có một cách thông thường:
["bar", "baz", "foo"].any? { |str| str == "foo" }
Và có một cách miễn phí:
["bar", "baz", "foo"].any?(&"foo".method(:==))
Cách ưa thích nên là cách dễ đọc nhất.
array.each{|e| foo(e)}
vẫn ngắn hơn :-) +1 dù sao
&method
?
[1,2,3].map(&Array.method(:new))
Nó tương đương với
def tag_names
@tag_names || tags.map { |tag| tag.name }.join(' ')
end
Mặc dù chúng ta cũng lưu ý rằng ampersand #to_proc
có thể hoạt động với bất kỳ lớp nào, không chỉ là Biểu tượng. Nhiều người Ruby chọn định nghĩa #to_proc
trên lớp Array:
class Array
def to_proc
proc { |receiver| receiver.send *self }
end
end
# And then...
[ 'Hello', 'Goodbye' ].map &[ :+, ' world!' ]
#=> ["Hello world!", "Goodbye world!"]
Ampersand &
hoạt động bằng cách gửi to_proc
tin nhắn trên toán hạng của nó, trong đoạn mã trên, thuộc lớp Array. Và vì tôi đã định nghĩa #to_proc
phương thức trên Array, nên dòng trở thành:
[ 'Hello', 'Goodbye' ].map { |receiver| receiver.send( :+, ' world!' ) }
Nó là viết tắt của tags.map { |tag| tag.name }.join(' ')
&
gọi to_proc
vào toán hạng của nó. Vì vậy, nó không đặc trưng cho phương thức bản đồ và trên thực tế hoạt động trên bất kỳ phương thức nào lấy một khối và chuyển một hoặc nhiều đối số cho khối.
Câu trả lời của Josh Lee gần như đúng ngoại trừ mã Ruby tương đương phải có như sau.
class Symbol
def to_proc
Proc.new do |receiver|
receiver.send self
end
end
end
không phải
class Symbol
def to_proc
Proc.new do |obj, *args|
obj.send self, *args
end
end
end
Với mã này, khi print [[1,'a'],[2,'b'],[3,'c']].map(&:first)
được thực thi, Ruby chia đầu vào đầu tiên [1,'a']
thành 1 và 'a' để cho obj
1 và args*
'a' để gây ra lỗi vì đối tượng Fixnum 1 không có phương thức tự (đó là: đầu tiên).
Khi [[1,'a'],[2,'b'],[3,'c']].map(&:first)
được thực thi;
:first
là một đối tượng Biểu tượng, do đó, khi &:first
được đưa cho một phương thức bản đồ làm tham số, Ký hiệu # to_proc được gọi.
map gửi tin nhắn cuộc gọi đến: first.to_proc với tham số [1,'a']
, ví dụ, :first.to_proc.call([1,'a'])
được thực thi.
Thủ tục to_proc trong lớp Symbol gửi một thông điệp gửi đến một đối tượng mảng ( [1,'a']
) với tham số (: đầu tiên), ví dụ, [1,'a'].send(:first)
được thực thi.
lặp đi lặp lại trên phần còn lại của các yếu tố trong [[1,'a'],[2,'b'],[3,'c']]
đối tượng.
Điều này giống như [[1,'a'],[2,'b'],[3,'c']].map(|e| e.first)
biểu thức thực thi .
[1,2,3,4,5,6].inject(&:+)
- tiêm mong đợi một lambda với hai tham số (ghi nhớ và vật phẩm) và :+.to_proc
cung cấp nó - Proc.new |obj, *args| { obj.send(self, *args) }
hoặc{ |m, o| m.+(o) }
Hai điều đang xảy ra ở đây, và điều quan trọng là phải hiểu cả hai.
Như được mô tả trong các câu trả lời khác, Symbol#to_proc
phương pháp đang được gọi.
Nhưng lý do to_proc
đang được gọi trên biểu tượng là vì nó được chuyển đến map
dưới dạng đối số khối. Đặt &
trước một đối số trong một cuộc gọi phương thức làm cho nó được truyền theo cách này. Điều này đúng với bất kỳ phương thức Ruby nào, không chỉ map
với các ký hiệu.
def some_method(*args, &block)
puts "args: #{args.inspect}"
puts "block: #{block.inspect}"
end
some_method(:whatever)
# args: [:whatever]
# block: nil
some_method(&:whatever)
# args: []
# block: #<Proc:0x007fd23d010da8>
some_method(&"whatever")
# TypeError: wrong argument type String (expected Proc)
# (String doesn't respond to #to_proc)
Nó Symbol
được chuyển đổi thành một Proc
vì nó được truyền vào dưới dạng một khối. Chúng tôi có thể chỉ ra điều này bằng cách cố gắng vượt qua một Proc .map
mà không cần ký hiệu:
arr = %w(apple banana)
reverse_upcase = proc { |i| i.reverse.upcase }
reverse_upcase.is_a?(Proc)
=> true
arr.map(reverse_upcase)
# ArgumentError: wrong number of arguments (1 for 0)
# (map expects 0 positional arguments and one block argument)
arr.map(&reverse_upcase)
=> ["ELPPA", "ANANAB"]
Mặc dù không cần phải chuyển đổi, phương thức này sẽ không biết cách sử dụng vì nó mong muốn một đối số khối. Vượt qua nó với &
cho.map
khối nó mong đợi.
Về cơ bản, nó thực hiện lệnh gọi phương thức tag.name
trên mỗi thẻ trong mảng.
Nó là một tốc ký đơn giản của ruby.
Mặc dù chúng tôi đã có câu trả lời tuyệt vời, nhưng nhìn qua góc nhìn của người mới bắt đầu tôi muốn thêm thông tin bổ sung:
Bản đồ (&: name) có nghĩa là gì trong Ruby?
Điều này có nghĩa là, bạn đang truyền một phương thức khác làm tham số cho hàm ánh xạ. (Trong thực tế, bạn đang chuyển một biểu tượng được chuyển đổi thành một Proc. Nhưng điều này không quan trọng trong trường hợp cụ thể này).
Điều quan trọng là bạn có một method
tên name
được sử dụng bởi phương thức bản đồ làm đối số thay vì block
kiểu truyền thống .
Đầu tiên, &:name
là một lối tắt cho &:name.to_proc
, trong đó :name.to_proc
trả về một Proc
(một cái gì đó tương tự, nhưng không giống với lambda) mà khi được gọi với một đối tượng là đối số (đầu tiên), gọi name
phương thức trên đối tượng đó.
Thứ hai, trong khi &
trong def foo(&block) ... end
người cải đạo một khối truyền cho foo
đến một Proc
, nó là đối diện khi áp dụng cho một Proc
.
Do đó, &:name.to_proc
là một khối lấy một đối tượng làm đối số và gọi name
phương thức trên nó, tức là { |o| o.name }
.
Nó giống như dưới đây:
def tag_names
if @tag_names
@tag_names
else
tags.map{ |t| t.name }.join(' ')
end