¿Qué significa map (& amp;: name) en Ruby?
-
07-07-2019 - |
Pregunta
Encontré este código en a RailsCast :
def tag_names
@tag_names || tags.map(&:name).join(' ')
end
¿Qué significa (& amp;: name)
en map (& amp;: name)
?
Solución
Es la abreviatura de tags.map (& amp;: name.to_proc) .join ('')
Si foo
es un objeto con un método to_proc
, puede pasarlo a un método como & amp; foo
, que llamará foo.to_proc
y úselo como bloque del método.
El método Symbol # to_proc
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
Otros consejos
Otra taquigrafía genial, desconocida para muchos, es
array.each(&method(:foo))
que es una abreviatura de
array.each { |element| foo(element) }
Al llamar al método (: foo)
tomamos un objeto Method
de self
que representa su método foo
y usó & amp;
para indicar que tiene un to_proc
método que lo convierte en un Proc
.
Esto es muy útil cuando desea hacer cosas con estilo sin puntos . 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.
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 & amp;
funciona enviando el mensaje to_proc
en su operando, que, en el código anterior, es de la clase Array. Y como definí el método #to_proc
en Array, la línea se convierte en:
[ 'Hello', 'Goodbye' ].map { |receiver| receiver.send( :+, ' world!' ) }
Es la abreviatura de tags.map {| tag | tag.name} .join ('')
tags.map(&:name)
es lo mismo que
tags.map{|tag| tag.name}
& amp;: name
solo usa el símbolo como el nombre del método a llamar.
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 (& amp;: first)
es ejecutado, Ruby divide la primera entrada [1, 'a']
en 1 y 'a' para dar obj
1 y args *
'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 (& amp;: first)
se ejecuta;
: first
es un objeto Symbol, por lo que cuando& amp;: first
se asigna a un método de mapa como parámetro, se invoca Symbol # to_proc.
El mapa -
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 objeto
[[1, 'a'], [2, 'b'], [3, 'c']]
.
Esto es lo mismo que ejecutar [[1, 'a'], [2, 'b'], [3, 'c']]. map (| e | e.first)
expresión.
Aquí están sucediendo dos cosas, y es importante comprender ambas.
Como se describe en otras respuestas, se llama al método Symbol # to_proc
.
Pero la razón por la que se invoca to_proc
en el símbolo es porque se pasa a map
como un argumento de bloque. Colocar & amp;
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 Símbolo
se convierte en un Proc
porque se pasa como un bloque. Podemos mostrar esto al tratar de pasar un proceso a .map
sin el ampersand:
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 & amp;
le da a .map
el bloque que espera.
(& amp;: name) es la abreviatura de (& amp;: name.to_proc) es lo mismo que tags.map {| t | t.name} .join ('')
to_proc se implementa realmente en C
Aunque ya tenemos excelentes respuestas, mirando a través de la perspectiva de un principiante me gustaría agregar la información adicional:
¿Qué significa map (& amp;: 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 método
llamado name
que será utilizado por el método map como argumento en lugar del bloque tradicional
estilo.
map (& amp;: name) 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)
Aquí : name
es el símbolo que apunta al método name
del objeto de etiqueta.
Cuando pasamos & amp;: name
a map
, tratará a name
como un objeto proc.
Para abreviar, tags.map (& amp;: name)
actúa como:
tags.map do |tag|
tag.name
end
significa
array.each(&:to_sym.to_proc)
Básicamente ejecuta la llamada al método tag.name
en cada etiqueta de la matriz.
Es una taquigrafía rubí simplificada.
Es lo mismo que a continuación:
def tag_names
if @tag_names
@tag_names
else
tags.map{ |t| t.name }.join(' ')
end