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

496

Encontrei esse código em um RailsCast :

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

O que significa (&:name)in map(&:name)?

collimarco
fonte
122
Eu ouvi isso chamado "cólon pretzel", a propósito.
Josh Lee
6
Haha Eu sei disso como um comercial. Eu nunca ouvi isso chamado "pretzel", mas isso faz sentido.
DragonFax
74
Chamá-lo de "cólon pretzel" é enganador, embora cativante. Não existe "&:" em rubi. Oe comercial (&) é um "operador de e comercial unário" com um símbolo: juntos. Se alguma coisa, é um "símbolo de pretzel". Apenas dizendo.
FontJul
3
tags.map (&: name) é uma espécie de tags.map {| s | s.name}
kaushal sharma 17/07/2016
3
sons "pretzel cólon" como uma condição médica dolorosa ... Mas eu gosto do nome para este símbolo :)
zmorris

Respostas:

517

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

Se fooé um objeto com um to_procmétodo, você pode passá-lo para um método como &foo, que o chamará foo.to_proce usará como bloco do método.

O Symbol#to_procmétodo 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
Josh Lee
fonte
42
Esta é uma resposta melhor que a minha.
267 Oliver N.
91
tags.map (: name.to_proc) é uma abreviação de tags.map {| tag | tag.name}
Simone Carletti
5
este não é o código Ruby válido, você ainda precisa o &, ou sejatags.map(&:name.to_proc).join(' ')
horseyguy
5
O símbolo # to_proc é implementado em C, não no Ruby, mas é assim que seria no Ruby.
Andrew Grimm
5
@AndrewGrimm foi adicionado pela primeira vez no Ruby on Rails, usando esse código. Foi então adicionado como um recurso ruby ​​nativo na versão 1.8.7.
Cameron Martin #:
175

Outra abreviação legal, desconhecida para muitos, é

array.each(&method(:foo))

que é uma abreviação de

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

Ao chamar method(:foo), pegamos um Methodobjeto selfque representa seu foométodo e usamos o &para significar que ele possui um to_proc método que o converte em a Proc.

Isso é muito útil quando você deseja fazer coisas sem estilo. Um exemplo é verificar se há alguma string em uma matriz igual à string "foo". Existe a maneira convencional:

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

E existe o caminho sem pontos:

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

A maneira preferida deve ser a mais legível.

Gerry
fonte
25
array.each{|e| foo(e)}é mais curto ainda :-) +1 de qualquer maneira
Jared Beck
Você poderia mapear um construtor de outra classe usando &method?
princípio holográfico
3
@finishingmove sim, eu acho. Tente isto[1,2,3].map(&Array.method(:new))
Gerry
78

É equivalente a

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

Embora também observemos que a #to_procmagia e comercial pode funcionar com qualquer classe, não apenas com o Symbol. Muitos Rubyists optam por definir #to_procna classe Array:

class Array
  def to_proc
    proc { |receiver| receiver.send *self }
  end
end

# And then...

[ 'Hello', 'Goodbye' ].map &[ :+, ' world!' ]
#=> ["Hello world!", "Goodbye world!"]

E comercial &funciona enviando to_procmensagem em seu operando, que, no código acima, é da classe Array. E desde que eu defini o #to_procmétodo na matriz, a linha se torna:

[ 'Hello', 'Goodbye' ].map { |receiver| receiver.send( :+, ' world!' ) }
Boris Stitnicky
fonte
Isso é ouro puro!
Kubak # 30/19
38

É uma abreviação de tags.map { |tag| tag.name }.join(' ')

Oliver N.
fonte
Não, está no Ruby 1.8.7 e acima.
Chuck
É um idioma simples para o mapa ou Ruby sempre interpreta o '&' de uma maneira específica?
collimarco
7
@ collimarco: Como jleedev diz em sua resposta, o &operador unário chama to_procseu operando. Portanto, não é específico para o método de mapa e, de fato, funciona em qualquer método que pega um bloco e passa um ou mais argumentos para o bloco.
Chuck
36
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.

Albert.Qing
fonte
1
A resposta que eu estava procurando, em vez de especificamente para procs (mas que era a pergunta solicitantes)
matrim_c
Boa resposta! esclarecido para mim também.
Apadana
14

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 esse código, quando print [[1,'a'],[2,'b'],[3,'c']].map(&:first)é executado, Ruby divide a primeira entrada [1,'a']em 1 e 'a' para fornecer obj1 e args*'a' para causar um erro, pois o objeto Fixnum 1 não possui o método self (que é: first).


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

  1. :firsté um objeto Symbol, portanto, quando &:firsté fornecido um método de mapa como parâmetro, o símbolo # to_proc é chamado.

  2. O mapa envia uma 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 classe Symbol envia uma mensagem de envio para um objeto de matriz ( [1,'a']) com o parâmetro (: first), por exemplo, [1,'a'].send(:first)é executado.

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

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

prosseek
fonte
1
A resposta de Josh Lee é absolutamente correto, como você pode ver por pensar [1,2,3,4,5,6].inject(&:+)- injetar espera um lambda com dois parâmetros (MEMO e item) e :+.to_procentrega-lo - Proc.new |obj, *args| { obj.send(self, *args) }ou{ |m, o| m.+(o) }
Uri Agassi
11

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

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

Mas o motivo pelo qual to_proco símbolo está sendo chamado é porque ele está sendo passado mapcomo um argumento de bloco. Colocar &na frente de um argumento em uma chamada de método faz com que seja passado dessa maneira. Isso é verdade para qualquer método Ruby, não apenas mapcom 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)

O Symbolé convertido em um Procporque é passado como um bloco. Podemos mostrar isso tentando passar um proc .mapsem 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, pois espera um argumento de bloco. Passá-lo &fornece .mapo bloco que ele espera.

devpuppy
fonte
Esta é honestamente a melhor resposta dada. Você explica o mecanismo por trás do e comercial e por que terminamos com um processo, que não recebi até sua resposta. Obrigado.
Fralcon
5

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

to_proc é realmente implementado em C

tessie
fonte
5

map (&: name) pega um objeto enumerável (tags no seu caso) e executa o método name para cada elemento / tag, produzindo cada valor retornado pelo método.

É uma abreviação de

array.map { |element| element.name }

que retorna a matriz de nomes de elementos (tags)

Sunda
fonte
3

Basicamente, executa a chamada de método tag.nameem cada tag na matriz.

É uma abreviação simplificada de rubi.

Olalekan Sogunle
fonte
2

Embora já tenhamos ótimas respostas, procurando uma perspectiva de iniciante, gostaria de adicionar as informações adicionais:

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

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

O importante é que você tenha um methodnome nameque será usado pelo método do mapa como argumento em vez do blockestilo tradicional .

Jonathan Duarte
fonte
2

Primeiro, &:nameé um atalho para &:name.to_proc, onde :name.to_procretorna a Proc(algo semelhante, mas não idêntico a um lambda) que, quando chamado com um objeto como argumento (primeiro), chama o namemétodo nesse objeto.

Segundo, enquanto &in def foo(&block) ... endconverte um bloco passado para fooa Proc, ele faz o oposto quando aplicado a a Proc.

Assim, &:name.to_procé um bloco que pega um objeto como argumento e chama o namemétodo nele, ie { |o| o.name }.

Christoph
fonte
1

Aqui :nameestá o símbolo que aponta para o método namedo objeto tag. Quando passamos &:namepara map, ele será tratado namecomo um objeto proc. Para resumir, tags.map(&:name)atua como:

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

Isso significa

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

É o mesmo que abaixo:

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