2009-08-01 30 views
428

tôi tìm thấy mã này trong a RailsCast:Bản đồ (&: name) có ý nghĩa gì trong Ruby?

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

nào (&:name) trong map(&:name) nghĩa là gì?

+106

Tôi đã nghe điều này được gọi là "pretzel colon", nhân tiện. –

+4

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

+1

Ngoài ra, bạn có thể thả các dấu ngoặc vuông 'tags.map &: name' cho mục nhập ngắn nhất thêm. – itsnikolay

Trả lời

450

Đó là viết tắt cho tags.map(&:name.to_proc).join(' ')

Nếu foo là một đối tượng với một phương pháp to_proc, sau đó bạn có thể vượt qua nó đến một phương pháp như &foo , sẽ gọi foo.to_proc và sử dụng làm khối của phương thức.

Phương thức Symbol#to_proc ban đầu được thêm bởi ActiveSupport nhưng đã được tích hợp vào Ruby 1.8.7. Đây là việc thực hiện:

class Symbol 
    def to_proc 
    Proc.new do |obj, *args| 
     obj.send self, *args 
    end 
    end 
end 
+34

Đây là câu trả lời hay hơn tôi. –

+79

tags.map (: name.to_proc) là một cách viết tắt cho tags.map {| tag | tag.name} –

+4

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

35

Đó là viết tắt cho tags.map { |tag| tag.name }.join(' ')

+0

Không, nó nằm trong Ruby 1.8.7 trở lên. – Chuck

+0

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

+0

@Chúc cảm ơn, đã hoàn nguyên về tính chính xác. –

67

Đó là tương đương với

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

Một tốc ký mát mẻ, chưa được biết đến nhiều, là

array.each(&method(:foo)) 

mà là một viết tắt cho

array.each { |element| foo(element) } 

Bằng cách gọi method(:foo) chúng tôi mất một đối tượng Method từ self đại diện cho phương thức foo và sử dụng số & để biểu thị rằng nó có số to_procmethod chuyển đổi nó thành Proc.

Điều này rất hữu ích khi bạn muốn làm mọi thứ điểm miễn phí kiểu. Một ví dụ là để kiểm tra xem có bất kỳ chuỗi nào trong một mảng bằng với chuỗi "foo" hay không. Có những cách thông thường:

["bar", "baz", "foo"].any? { |str| str == "foo" } 

Và có cách điểm miễn phí:

["bar", "baz", "foo"].any?(&"foo".method(:==)) 

Cách ưa thích nên là một dễ đọc nhất.

+22

'array.each {| e | foo (e)} 'là ngắn hơn vẫn còn :-) +1 anyways –

+6

@JaredBeck Yeap! Ngắn hơn nhưng không phải là điểm miễn phí :) – Gerry

+0

Và nó là đáng ngạc nhiên nhanh chóng. –

36

Trong khi chúng ta cũng lưu ý rằng dấu và #to_proc phép thuật có thể hoạt động với bất kỳ lớp nào, không chỉ là Biểu tượng. Nhiều Rubyists chọn để xác định #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!"] 

Ký hiệu & & hoạt động bằng cách gửi tin nhắn trên to_proc toán hạng của nó, trong đó, đoạn mã trên, là của lớp Array. Và kể từ khi tôi định nghĩa #to_proc phương pháp trên Array, dòng trở thành:

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

Josh Lee Câu trả lời là gần như chính xác ngoại trừ các mã Ruby tương đương cần phải có được như sau.

class Symbol 
    def to_proc 
    Proc.new do |receiver| 
     receiver.send self 
    end 
    end 
end 

không

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 hiện, Ruby chia đầu vào đầu tiên [1,'a'] vào 1 và 'a' để cung cấp cho obj 1 và args* 'a' để gây ra một lỗi như Đối tượng Fixnum 1 không có phương thức tự (tức là: đầu tiên).


Khi [[1,'a'],[2,'b'],[3,'c']].map(&:first) được thực thi;

  1. :first là một đối tượng Symbol, vì vậy khi &:first được trao cho một phương pháp bản đồ như một tham số, Symbol # to_proc được gọi.

  2. bản đồ sẽ gửi thông báo cuộc gọi đến: first.to_proc với thông số [1,'a'], ví dụ: :first.to_proc.call([1,'a']) được thực thi.

  3. thủ tục to_proc trong lớp Biểu tượng gửi thông báo gửi đến đối tượng mảng ([1,'a']) với thông số (: đầu tiên), ví dụ: [1,'a'].send(:first) được thực thi.

  4. lặp qua các phần tử còn lại trong đối tượng [[1,'a'],[2,'b'],[3,'c']].

Điều này cũng giống như thực hiện biểu thức [[1,'a'],[2,'b'],[3,'c']].map(|e| e.first).

+1

Câu trả lời của Josh Lee là _absolutely_ đúng, như bạn có thể thấy bằng cách suy nghĩ về '[1,2,3,4,5,6] .inject (&: +)' - inject mong đợi một lambda với hai tham số (bản ghi nhớ và mục) và ': +. to_proc' phân phối nó -' Proc.new | obj, * args | {obj.send (self, * args)} 'hoặc' {| m, o | m. + (o)} ' –

6

Hai điều đang diễn 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, phương pháp Symbol#to_proc đ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 làm đối số khối. Đặt & trước đố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ỉ là 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) 

Symbol được chuyển thành Proc vì được chuyển thành một khối. Chúng tôi có thể hiển thị này bằng cách cố gắng để vượt qua một proc để .map không dấu và:

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ù nó không cần phải được chuyển đổi, phương pháp này sẽ không biết làm thế nào để sử dụng nó bởi vì nó hy vọng một cuộc tranh luận khối . Vượt qua nó với & cung cấp cho .map khối dự kiến.

+0

Đây là câu trả lời đúng nhất. 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

0

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

def tag_names 
    if @tag_names 
    @tag_names 
    else 
    tags.map{ |t| t.name }.join(' ') 
end 
0

Đây :name là biểu tượng thời điểm đó với phương pháp name của đối tượng thẻ. Khi chúng tôi vượt qua &:name đến map, nó sẽ xử lý name làm đối tượng proc. Đối với ngắn, tags.map(&:name) hoạt động như:

tags.map do |tag| 
    tag.name 
end 
5

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

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

18
tags.map(&:name) 

cũng giống như

tags.map{|tag| tag.name} 

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

+0

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

0

nó có nghĩa là

array.each(&:to_sym.to_proc) 
1

Mặc dù chúng tôi có câu trả lời tuyệt vời rồi, nhìn qua một góc nhìn của một người mới bắt đầu tôi muốn thêm thông tin bổ sung:

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

Điều này có nghĩa là bạn đang chuyển một phương thức khác làm tham số cho hàm bản đồ. (Trong thực tế bạn đang đi qua 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ó method có tên name sẽ được phương thức bản đồ sử dụng làm đối số thay vì kiểu block truyền thống.