¿Qué significa map (&: name) en Ruby?

496

Encontré este código en un RailsCast :

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

¿Qué significa el (&:name)en map(&:name)?

collimarco
fuente
122
He oído esto llamado "colon de pretzel", por cierto.
Josh Lee
66
Jaja. Lo sé como un Ampersand. Nunca he oído que se llame "pretzel", pero eso tiene sentido.
DragonFax
74
Llamarlo "colon de pretzel" es engañoso, aunque pegadizo. No hay "&:" en ruby. El ampersand (&) es un "operador de ampersand unario" con un símbolo: símbolo. En todo caso, es un "símbolo de pretzel". Solo digo.
fontno
3
tags.map (&: name) es un tipo de tags.map {| s | s.name}
kaushal sharma
3
"pretzel colon" suena como una condición médica dolorosa ... pero me gusta el nombre de este símbolo :)
zmorris

Respuestas:

517

Es taquigrafía para tags.map(&:name.to_proc).join(' ')

Si fooes un objeto con un to_procmétodo, puede pasarlo a un método como &foo, que lo llamará foo.to_procy lo usará como bloque del método.

El Symbol#to_procmé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
Josh Lee
fuente
42
Esta es una mejor respuesta que la mía.
Oliver N.
9191
tags.map (: name.to_proc) es una abreviatura de tags.map {| tag | tag.name}
Simone Carletti
55
este no es un código ruby ​​válido, todavía necesita el &, es decirtags.map(&:name.to_proc).join(' ')
horseyguy
55
El símbolo # to_proc se implementa en C, no en Ruby, pero así es como se vería en Ruby.
Andrew Grimm el
55
@AndrewGrimm se agregó por primera vez en Ruby on Rails, utilizando ese código. Luego se agregó como una característica nativa de rubí en la versión 1.8.7.
Cameron Martin
175

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 Methodobjeto selfque representa su foomé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.

Gerry
fuente
25
array.each{|e| foo(e)}es aún más corto :-) +1 de todos modos
Jared Beck
¿Podría asignar un constructor de otra clase usando &method?
principio holográfico
3
@finishingmove, sí, supongo. Prueba esto[1,2,3].map(&Array.method(:new))
Gerry
78

Es equivalente a

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

Si bien también tengamos en cuenta que el ampersand #to_procmagic puede funcionar con cualquier clase, no solo Symbol. Muchos rubíes eligen definir #to_procen 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_procmensaje en su operando, que, en el código anterior, es de la clase Array. Y desde que definí el #to_procmétodo en Array, la línea se convierte en:

[ 'Hello', 'Goodbye' ].map { |receiver| receiver.send( :+, ' world!' ) }
Boris Stitnicky
fuente
¡Esto es oro puro!
kubak
38

Es taquigrafía para tags.map { |tag| tag.name }.join(' ')

Oliver N.
fuente
No, está en Ruby 1.8.7 y superior.
Chuck
¿Es un idioma simple para el mapa o Ruby siempre interpreta el '&' de una manera particular?
collimarco
77
@collimarco: Como dice jleedev en su respuesta, el &operador unario llama to_proca 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.
Chuck
36
tags.map(&:name)

es lo mismo que

tags.map{|tag| tag.name}

&:name solo usa el símbolo como el nombre del método a llamar.

Albert.Qing
fuente
1
La respuesta que estaba buscando, en lugar de específicamente para los procs (pero esa era la pregunta de los solicitantes)
matrim_c
¡Buena respuesta! me aclaró bien.
apadana
14

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

  1. :firstes un objeto Symbol, por lo que cuando &:firstse le da un método de mapa como parámetro, se invoca Symbol # to_proc.

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

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

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

prosseek
fuente
1
La respuesta de Josh Lee es absolutamente correcta, como se puede ver por pensar [1,2,3,4,5,6].inject(&:+)- Inyectar espera una lambda con dos parámetros (MEMO) y el tema y :+.to_proclo entrega - Proc.new |obj, *args| { obj.send(self, *args) }o{ |m, o| m.+(o) }
Uri Agassi
11

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_procse llama al símbolo es porque se pasa mapcomo 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 mapcon 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 Symbolse convierte en a Procporque se pasa como un bloque. Podemos mostrar esto tratando de pasar un proceso .mapsin 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 .mapel bloqueo que espera.

devpuppy
fuente
Esta es honestamente la mejor respuesta dada. Explicas el mecanismo detrás del ampersand y por qué terminamos con un proceso, que no obtuve hasta tu respuesta. Gracias.
Fralcon
5

(&: name) es la abreviatura de (&: name.to_proc) es igual que tags.map{ |t| t.name }.join(' ')

to_proc se implementa realmente en C

Tessie
fuente
5

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)

Sunda
fuente
3

Básicamente ejecuta la llamada al método tag.nameen cada etiqueta de la matriz.

Es una taquigrafía rubí simplificada.

Olalekan Sogunle
fuente
2

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 methodnombre nameque será utilizado por el método de mapa como argumento en lugar del blockestilo tradicional .

Jonathan Duarte
fuente
2

Primero, &:namees un atajo para &:name.to_proc, donde :name.to_procdevuelve 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 namemétodo en ese objeto.

En segundo lugar, mientras que &en def foo(&block) ... endconversiones un bloque pasa a fooa Proc, hace lo contrario cuando se aplica a a Proc.

Por lo tanto, &:name.to_proces un bloque que toma un objeto como argumento y llama al namemétodo sobre él, es decir { |o| o.name }.

Christoph
fuente
1

Aquí :nameestá el símbolo que apunta al método namede etiqueta de objeto. Cuando pasamos &:namea map, se tratará namecomo un objeto proc. Para abreviar, tags.map(&:name)actúa como:

tags.map do |tag|
  tag.name
end
timlentse
fuente
1

significa

array.each(&:to_sym.to_proc)
mminski
fuente
0

Es lo mismo a continuación:

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