O que map (&: name) significa em Ruby?

Eu encontrei este código em um RailsCast :

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

O que o (&:name) no map(&:name) significa?

   

É uma abreviação para tags.map(&:name.to_proc).join(' ')

Se foo é um object com um método to_proc , então você pode passá-lo para um método como &foo , que irá chamar foo.to_proc e usar isso como o bloco do método.

O método Symbol#to_proc foi originalmente adicionado pelo ActiveSupport, mas foi integrado ao Ruby 1.8.7. Esta é a sua implementação:

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

Outra taquigrafia legal, desconhecida para muitos, é

 array.each(&method(:foo)) 

que é uma forma abreviada de

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

Ao chamarmos o method(:foo) , pegamos um object Method do self que representa seu método foo e usamos o & para significar que ele possui um método to_proc que o converte em Proc .

Isso é muito útil quando você quer fazer coisas sem pontos . Um exemplo é verificar se existe alguma string em uma matriz que seja igual à string "foo" . Existe a maneira convencional:

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

E há o caminho livre de pontos:

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

O caminho preferido deve ser o mais legível.

É equivalente a

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

Enquanto vamos notar também que o e comercial #to_proc magic pode funcionar com qualquer class, não apenas com Symbol. Muitos Rubistas optam por definir #to_proc na class Array:

 class Array def to_proc proc { |receiver| receiver.send *self } end end # And then... [ 'Hello', 'Goodbye' ].map &[ :+, ' world!' ] #=> ["Hello world!", "Goodbye world!"] 

Ampersand & funciona enviando a mensagem to_proc em seu operando, que, no código acima, é da class Array. E desde que eu #to_proc método #to_proc no Array, a linha se torna:

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

É uma abreviatura de tags.map { |tag| tag.name }.join(' ') tags.map { |tag| tag.name }.join(' ')

 tags.map(&:name) 

é o mesmo que

 tags.map{|tag| tag.name} 

&:name apenas usa o símbolo como o nome do método a ser chamado.

A resposta de Josh Lee está quase correta, exceto que o código Ruby equivalente deveria ter sido o seguinte.

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

não

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

Com este código, quando a print [[1,'a'],[2,'b'],[3,'c']].map(&:first) é executada, Ruby divide a primeira input [1,'a'] em 1 e’ a ‘para dar obj 1 e args* ‘ a ‘para causar um erro, pois o object Fixnum 1 não possui o método self (que é: primeiro).


Quando [[1,'a'],[2,'b'],[3,'c']].map(&:first) é executado;

  1. :first é um object Symbol, então quando &:first é dado a um método map como parâmetro, Symbol # to_proc é invocado.

  2. map envia mensagem de chamada para: first.to_proc com o parâmetro [1,'a'] , por exemplo :first.to_proc.call([1,'a']) é executado.

  3. O procedimento to_proc na class Symbol envia uma mensagem de envio para um object de matriz ( [1,'a'] ) com o parâmetro (: first), por exemplo, [1,'a'].send(:first) é executado.

  4. itera sobre o resto dos elementos no object [[1,'a'],[2,'b'],[3,'c']] .

É o mesmo que executar a expressão [[1,'a'],[2,'b'],[3,'c']].map(|e| e.first) .

Duas coisas estão acontecendo aqui e é importante entender as duas coisas.

Conforme descrito em outras respostas, o método Symbol#to_proc está sendo chamado.

Mas a razão pela qual to_proc está sendo chamado no símbolo é porque ele está sendo passado para map como um argumento de bloco. Colocar & na frente de um argumento em uma chamada de método faz com que seja passado dessa forma. Isso vale para qualquer método Ruby, não apenas para map com símbolos.

 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: # some_method(&"whatever") # TypeError: wrong argument type String (expected Proc) # (String doesn't respond to #to_proc) 

O Symbol é convertido em um Proc porque é passado como um bloco. Podemos mostrar isso tentando passar um proc para .map sem o “e” comercial:

 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"] 

Mesmo que não precise ser convertido, o método não saberá como usá-lo porque espera um argumento de bloco. Passando isto com &.map o bloco que ele espera.

(&: name) é a abreviação de (&: name.to_proc) é igual a tags.map{ |t| t.name }.join(' ') tags.map{ |t| t.name }.join(' ')

to_proc é realmente implementado em C

Embora já tenhamos ótimas respostas, olhando através de uma perspectiva de um iniciante, gostaria de adicionar as informações adicionais:

O que map (&: name) significa em Ruby?

Isso significa que você está passando outro método como parâmetro para a function map. (Na realidade, você está passando um símbolo que é convertido em proc. Mas isso não é importante nesse caso específico).

O importante é que você tenha um method chamado name que será usado pelo método map como um argumento em vez do estilo de block tradicional.

Aqui :name é o símbolo que aponta para o name do método do object de tag. Quando passamos &:name para map , ele tratará name como um object proc. Para resumir, tags.map(&:name) atua como:

 tags.map do |tag| tag.name end 

Isso significa

 array.each(&:to_sym.to_proc) 

É o mesmo que abaixo:

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