Encontré este código en un RailsCast :
def tag_names
@tag_names || tags.map(&:name).join(' ')
end
¿Qué significa el (&:name)
en map(&:name)
?
Encontré este código en un RailsCast :
def tag_names
@tag_names || tags.map(&:name).join(' ')
end
¿Qué significa el (&:name)
en map(&:name)
?
Respuestas:
Es taquigrafía para tags.map(&:name.to_proc).join(' ')
Si foo
es un objeto con un to_proc
método, puede pasarlo a un método como &foo
, que lo llamará foo.to_proc
y lo usará como bloque del método.
El Symbol#to_proc
método fue agregado originalmente por ActiveSupport pero se ha integrado en Ruby 1.8.7. Esta es su implementación:
class Symbol
def to_proc
Proc.new do |obj, *args|
obj.send self, *args
end
end
end
&
, es decirtags.map(&:name.to_proc).join(' ')
Otra taquigrafía genial, desconocida para muchos, es
array.each(&method(:foo))
que es una abreviatura de
array.each { |element| foo(element) }
Al llamar method(:foo)
, tomamos un Method
objeto self
que representa su foo
método, y lo usamos &
para indicar que tiene un to_proc
método que lo convierte en a Proc
.
Esto es muy útil cuando quieres hacer cosas sin estilo. Un ejemplo es verificar si hay alguna cadena en una matriz que sea igual a la cadena "foo"
. Existe la forma convencional:
["bar", "baz", "foo"].any? { |str| str == "foo" }
Y existe la forma sin puntos:
["bar", "baz", "foo"].any?(&"foo".method(:==))
La forma preferida debería ser la más legible.
array.each{|e| foo(e)}
es aún más corto :-) +1 de todos modos
&method
?
[1,2,3].map(&Array.method(:new))
Es equivalente a
def tag_names
@tag_names || tags.map { |tag| tag.name }.join(' ')
end
Si bien también tengamos en cuenta que el ampersand #to_proc
magic puede funcionar con cualquier clase, no solo Symbol. Muchos rubíes eligen definir #to_proc
en la clase de matriz:
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 un to_proc
mensaje en su operando, que, en el código anterior, es de la clase Array. Y desde que definí el #to_proc
método en Array, la línea se convierte en:
[ 'Hello', 'Goodbye' ].map { |receiver| receiver.send( :+, ' world!' ) }
Es taquigrafía para tags.map { |tag| tag.name }.join(' ')
&
operador unario llama to_proc
a su operando. Por lo tanto, no es específico del método de mapa, y de hecho funciona en cualquier método que tome un bloque y pase uno o más argumentos al bloque.
La respuesta de Josh Lee es casi correcta, excepto que el código Ruby equivalente debería haber sido el siguiente.
class Symbol
def to_proc
Proc.new do |receiver|
receiver.send self
end
end
end
no
class Symbol
def to_proc
Proc.new do |obj, *args|
obj.send self, *args
end
end
end
Con este código, cuando print [[1,'a'],[2,'b'],[3,'c']].map(&:first)
se ejecuta, Ruby divide la primera entrada [1,'a']
en 1 y 'a' para dar obj
1 yargs*
'a' para causar un error ya que el objeto Fixnum 1 no tiene el método self (que es: primero).
Cuando [[1,'a'],[2,'b'],[3,'c']].map(&:first)
se ejecuta;
:first
es un objeto Symbol, por lo que cuando &:first
se le da un método de mapa como parámetro, se invoca Symbol # to_proc.
map envía un mensaje de llamada a: first.to_proc con el parámetro [1,'a']
, por ejemplo, :first.to_proc.call([1,'a'])
se ejecuta.
El procedimiento to_proc en la clase Symbol envía un mensaje de envío a un objeto de matriz ( [1,'a']
) con el parámetro (: primero), por ejemplo, [1,'a'].send(:first)
se ejecuta.
itera sobre el resto de los elementos en el [[1,'a'],[2,'b'],[3,'c']]
objeto.
Esto es lo mismo que ejecutar [[1,'a'],[2,'b'],[3,'c']].map(|e| e.first)
expresión.
[1,2,3,4,5,6].inject(&:+)
- Inyectar espera una lambda con dos parámetros (MEMO) y el tema y :+.to_proc
lo entrega - Proc.new |obj, *args| { obj.send(self, *args) }
o{ |m, o| m.+(o) }
Aquí están sucediendo dos cosas, y es importante comprender ambas.
Como se describe en otras respuestas, el Symbol#to_proc
se llama método.
Pero la razón por la que to_proc
se llama al símbolo es porque se pasa map
como un argumento de bloque. Colocar &
delante de un argumento en una llamada a un método hace que se pase de esta manera. Esto es cierto para cualquier método de Ruby, no solo map
con 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: #<Proc:0x007fd23d010da8>
some_method(&"whatever")
# TypeError: wrong argument type String (expected Proc)
# (String doesn't respond to #to_proc)
El Symbol
se convierte en a Proc
porque se pasa como un bloque. Podemos mostrar esto tratando de pasar un proceso .map
sin el signo y:
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"]
Aunque no es necesario convertirlo, el método no sabrá cómo usarlo porque espera un argumento de bloque. Pasarlo con &
da .map
el bloqueo que espera.
mapa (&: nombre) toma un objeto enumerable (etiquetas en su caso) y ejecuta el método de nombre para cada elemento / etiqueta, generando cada valor devuelto por el método.
Es una abreviatura de
array.map { |element| element.name }
que devuelve la matriz de nombres de elementos (etiquetas)
Básicamente ejecuta la llamada al método tag.name
en cada etiqueta de la matriz.
Es una taquigrafía rubí simplificada.
Aunque ya tenemos excelentes respuestas, mirando desde la perspectiva de un principiante me gustaría agregar la información adicional:
¿Qué significa map (&: name) en Ruby?
Esto significa que está pasando otro método como parámetro a la función de mapa. (En realidad, está pasando un símbolo que se convierte en un proceso. Pero esto no es tan importante en este caso particular).
Lo importante es que tenga un method
nombre name
que será utilizado por el método de mapa como argumento en lugar del block
estilo tradicional .
Primero, &:name
es un atajo para &:name.to_proc
, donde :name.to_proc
devuelve un Proc
(algo que es similar, pero no idéntico a un lambda) que cuando se llama con un objeto como (primer) argumento, llama al name
método en ese objeto.
En segundo lugar, mientras que &
en def foo(&block) ... end
conversiones un bloque pasa a foo
a Proc
, hace lo contrario cuando se aplica a a Proc
.
Por lo tanto, &:name.to_proc
es un bloque que toma un objeto como argumento y llama al name
método sobre él, es decir { |o| o.name }
.
Es lo mismo a continuación:
def tag_names
if @tag_names
@tag_names
else
tags.map{ |t| t.name }.join(' ')
end