Bản đồ (&: name) có nghĩa là gì trong Ruby?


496

Tôi tìm thấy mã này trong RailsCast :

def tag_names
  @tag_names || tags.map(&:name).join(' ')
end

(&:name)trong map(&:name)bình?


122
Nhân tiện, tôi đã nghe cái này được gọi là Pret Pretzel col.
Josh Lee

6
Haha. Tôi biết rằng như là một Ampersand. Tôi chưa bao giờ nghe nó được gọi là "bánh quy cây" nhưng điều đó có ý nghĩa.
DragonFax

74
Gọi nó là "Pretzel đại tràng" là sai lệch, mặc dù hấp dẫn. Không có "&:" trong ruby. Dấu và (&) là một "toán tử ampersand đơn nguyên" với một biểu tượng được đẩy cùng nhau. Nếu bất cứ điều gì, đó là một "biểu tượng bánh quy cây". Chỉ cần nói.
fontno

3
tags.map (&: name) được sắp xếp từ tags.map {| s | s.name}
shaus kaushal

3
"Đại tràng Pretzel" nghe có vẻ như là một tình trạng y tế đau đớn ... nhưng tôi thích tên của biểu tượng này :)
zmorris

Câu trả lời:


517

Nó là viết tắt của tags.map(&:name.to_proc).join(' ')

Nếu foolà một đối tượng với một to_procphươ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_procvà sử dụng nó làm khối của phương thức.

Các Symbol#to_procphươ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

42
Đây là một câu trả lời tốt hơn của tôi.
Oliver N.

91
tags.map (: name.to_proc) tự nó là một tốc ký cho tags.map {| tag | tag.name}
Simone Carletti

5
đây không phải là mã ruby ​​hợp lệ, bạn vẫn cần &, tức làtags.map(&:name.to_proc).join(' ')
Horseyguy

5
Biểu tượng # to_proc được triển khai bằng C, không phải trong Ruby, nhưng đó là giao diện của Ruby.
Andrew Grimm

5
@AndrewGrimm lần đầu tiên được thêm vào trong Ruby on Rails, sử dụng mã đó. Sau đó, nó đã được thêm vào như một tính năng ruby ​​bản địa trong phiên bản 1.8.7.
Cameron Martin

175

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 foophươ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.


25
array.each{|e| foo(e)}vẫn ngắn hơn :-) +1 dù sao
Jared Beck

Bạn có thể ánh xạ một constructor của một lớp khác bằng cách sử dụng &method?
nguyên tắc ba chiều

3
@finishingmove vâng tôi đoán vậy. Hãy thử điều này[1,2,3].map(&Array.method(:new))
Gerry

78

Nó tương đương với

def tag_names
  @tag_names || tags.map { |tag| tag.name }.join(' ')
end

45

Mặc dù chúng ta cũng lưu ý rằng ampersand #to_proccó 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_proctrê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_proctin 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_procphương thức trên Array, nên dòng trở thành:

[ 'Hello', 'Goodbye' ].map { |receiver| receiver.send( :+, ' world!' ) }

Đây là vàng nguyên chất!
kubak

38

Nó là viết tắt của tags.map { |tag| tag.name }.join(' ')


Không, đó là trong Ruby 1.8.7 trở lên.
Chuck

Đây có phải là một thành ngữ đơn giản cho bản đồ hay Ruby luôn diễn giải '&' theo một cách cụ thể?
collimarco

7
@collimarco: Như jleedev đã nói trong câu trả lời của mình, toán tử đơn nguyên &gọi to_procvà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.
Chuck

36
tags.map(&:name)

giống như

tags.map{|tag| tag.name}

&:name chỉ sử dụng ký hiệu làm tên phương thức được gọi.


1
Câu trả lời tôi đang tìm kiếm, thay vì cụ thể cho procs (nhưng đó là câu hỏi của người yêu cầu)
matrim_c

Câu trả lời tốt đẹp! làm rõ cho tôi tốt.
apadana

14

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 obj1 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;

  1. :firstlà 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.

  2. 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.

  3. 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.

  4. 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
Câu trả lời của Josh Lee là hoàn toàn chính xác, như bạn có thể thấy bằng cách suy nghĩ về [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_proccung cấp nó - Proc.new |obj, *args| { obj.send(self, *args) }hoặc{ |m, o| m.+(o) }
Uri Agassi

11

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_procphươ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 mapdướ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ỉ mapvớ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)

Symbolđược chuyển đổi thành một Procvì 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 .mapmà 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.


Đây thực sự là câu trả lời tốt nhất được đưa ra. Bạn giải thích cơ chế đằng sau ký hiệu và lý do tại sao chúng tôi kết thúc với một Proc, mà tôi đã không nhận được cho đến khi câu trả lời của bạn. Cảm ơn bạn.
Fralcon

5

(&: name) là viết tắt của (&: name.to_proc) nó giống như tags.map{ |t| t.name }.join(' ')

to_proc thực sự được thực hiện trong C


5

map (&: name) lấy một đối tượng có thể đếm được (các thẻ trong trường hợp của bạn) và chạy phương thức tên cho từng thành phần / thẻ, xuất ra từng giá trị được trả về từ phương thức.

Nó là một tốc ký cho

array.map { |element| element.name }

trong đó trả về mảng tên phần tử (thẻ)


3

Về cơ bản, nó thực hiện lệnh gọi phương thức tag.nametrên mỗi thẻ trong mảng.

Nó là một tốc ký đơn giản của ruby.


2

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 methodtên nameđược sử dụng bởi phương thức bản đồ làm đối số thay vì blockkiểu truyền thống .


2

Đầu tiên, &:namelà một lối tắt cho &:name.to_proc, trong đó :name.to_proctrả 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 namephương thức trên đối tượng đó.

Thứ hai, trong khi &trong def foo(&block) ... endngườ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_proclà một khối lấy một đối tượng làm đối số và gọi namephương thức trên nó, tức là { |o| o.name }.


1

Đây :namelà biểu tượng trỏ đến phương thức namecủa đối tượng thẻ. Khi chúng tôi vượt qua &:nameđể map, nó sẽ coi namenhư một đối tượng proc. Nói ngắn gọn, tags.map(&:name)hoạt động như:

tags.map do |tag|
  tag.name
end


0

Nó giống như dưới đây:

def tag_names
  if @tag_names
    @tag_names
  else
    tags.map{ |t| t.name }.join(' ')
end
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.