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?

Foi útil?

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;

  1. :first é um objeto Symbol, então quando &:first é dado a um método de mapa como um parâmetro, Símbolo # to_proc é invocado.

  2. mapa envia mensagem de chamada para: first.to_proc com [1,'a'] parâmetro, por exemplo, :first.to_proc.call([1,'a']) é executado.

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

  4. 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 &.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
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top