O que map (&: nome) significa em Ruby?
-
07-07-2019 - |
Pergunta
Eu encontrei este código na um Railscast :
def tag_names
@tag_names || tags.map(&:name).join(' ')
end
O que faz a (&:name)
em map(&:name)
média?
Solução
É de abreviação para tags.map(&:name.to_proc).join(' ')
Se foo
é um objeto 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 por ActiveSupport mas foi integrado o 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
Outras dicas
Outra forma abreviada legal, desconhecido para muitos, é
array.each(&method(:foo))
que é um atalho para
array.each { |element| foo(element) }
Ao chamar method(:foo)
tomamos um objeto Method
de self
que representa seu método foo
, e usou o &
para significar que ele tem uma to_proc
método que converte em um Proc
.
Isto é muito útil quando você quer fazer as coisas sem ponto estilo. Um exemplo é o de verificar se há qualquer string em uma matriz que é igual ao "foo"
string. Não é a maneira convencional:
["bar", "baz", "foo"].any? { |str| str == "foo" }
E não é a maneira livre de ponto:
["bar", "baz", "foo"].any?(&"foo".method(:==))
A maneira preferida deve ser o mais legível.
É equivalente a
def tag_names
@tag_names || tags.map { |tag| tag.name }.join(' ')
end
Enquanto vamos também observar que a magia e comercial #to_proc
pode trabalhar com qualquer classe, não apenas Symbol. Muitos Rubistas optar por definir #to_proc
na classe 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 através do envio de mensagens to_proc
em seu operando, o que, no código acima, é de classe Array. E desde que eu definida método #to_proc
em Array, a linha torna-se:
[ 'Hello', 'Goodbye' ].map { |receiver| receiver.send( :+, ' world!' ) }
É de abreviação para tags.map { |tag| tag.name }.join(' ')
tags.map(&:name)
é o mesmo que
tags.map{|tag| tag.name}
&:name
apenas utiliza o símbolo como o nome do método a ser chamado.
A resposta de Josh Lee é quase correta, exceto que o código Ruby equivalente deveria ter sido como segue.
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 print [[1,'a'],[2,'b'],[3,'c']].map(&:first)
é executado, Ruby divide o primeiro [1,'a']
entrada em 1 e 'a' para dar obj
1 e args*
'a' para provocar um erro como Fixnum objeto 1 não tem a auto método (que é :primeiro).
Quando [[1,'a'],[2,'b'],[3,'c']].map(&:first)
é executado;
-
:first
é um objeto Symbol, então quando&:first
é dado a um método de mapa como um parâmetro, Símbolo # to_proc é invocado. -
mapa envia mensagem de chamada para: first.to_proc com
[1,'a']
parâmetro, por exemplo,:first.to_proc.call([1,'a'])
é executado. -
procedimento to_proc na classe Symbol envia uma mensagem Enviar a um objeto array (
[1,'a']
) com o parâmetro (: em primeiro lugar)., Por exemplo,[1,'a'].send(:first)
é executado -
itera sobre o resto dos elementos em objeto
[[1,'a'],[2,'b'],[3,'c']]
.
Este é o mesmo que executar expressão [[1,'a'],[2,'b'],[3,'c']].map(|e| e.first)
.
Duas coisas estão acontecendo aqui, e é importante compreender ambos.
Como descrito em outras respostas, o método Symbol#to_proc
está sendo chamado.
Mas o to_proc
razão 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 por ali. Isto é verdade para qualquer método Ruby, não apenas 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: #<Proc:0x007fd23d010da8>
some_method(&"whatever")
# TypeError: wrong argument type String (expected Proc)
# (String doesn't respond to #to_proc)
O Symbol
é convertido em um Proc
porque ele é passado como um bloco. Nós podemos mostrar isso tentando passar um proc para .map
sem o 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 precisam ser convertidos, o método não vai saber como usá-lo porque ele espera um argumento bloco. Passando-lo com &
dá .map
o bloco espera.
(&: nome) é curto para (&: name.to_proc) é igual tags.map{ |t| t.name }.join(' ')
to_proc é realmente implementado em C
Embora tenhamos grandes respostas já, olhando através de uma perspectiva de um novato eu gostaria de adicionar a informações adicionais:
O que mapa? (&: Nome) significa em Ruby
Isto significa que você está passando um outro método como parâmetro para a função de mapa. (Na realidade você está passando um símbolo que é convertido em um proc. Mas isso não é tão importante, neste caso particular).
O que é importante é que você tem um method
chamado name
que será usado pelo método de mapa como um argumento em vez do estilo block
tradicional.
map (&: nome). leva um objeto enumerável (marcas no seu caso) e executa o método de nome para cada elemento / tag, a saída de cada valor retornado do método
É um atalho para
array.map { |element| element.name }
o qual devolve o conjunto de elemento (tag) nomes
Aqui :name
é o símbolo que apontam para a name
método de marca de objeto.
Quando passamos &:name
para map
, ele vai tratar name
como um objeto proc.
Para breve, tags.map(&:name)
age como:
tags.map do |tag|
tag.name
end
isso significa
array.each(&:to_sym.to_proc)
É basicamente executar o tag.name
chamada de método em cada tag na matriz.
É uma forma abreviada de rubi simplificada.
É mesmo como abaixo:
def tag_names
if @tag_names
@tag_names
else
tags.map{ |t| t.name }.join(' ')
end