开发者

What does map(&:name) mean in Ruby?

开发者 https://www.devze.com 2023-02-23 07:05 出处:网络
I found this code in a RailsCast: def tag_names @tag_names || tags.map(&:name).join(\' \') end What 开发者_如何学运维does the (&:name) in map(&:name) mean?It\'s shorthand for tags.map(&

I found this code in a RailsCast:

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

What 开发者_如何学运维does the (&:name) in map(&:name) mean?


It's shorthand for tags.map(&:name.to_proc).join(' ')

If foo is an object with a to_proc method, then you can pass it to a method as &foo, which will call foo.to_proc and use that as the method's block.

The Symbol#to_proc method was originally added by ActiveSupport but has been integrated into Ruby 1.8.7. This is its implementation:

class Symbol
  def to_proc
    Proc.new do |obj, *args|
      obj.send self, *args
    end
  end
end


Another cool shorthand, unknown to many, is

array.each(&method(:foo))

which is a shorthand for

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

By calling method(:foo) we took a Method object from self that represents its foo method, and used the & to signify that it has a to_proc method that converts it into a Proc.

This is very useful when you want to do things point-free style. An example is to check if there is any string in an array that is equal to the string "foo". There is the conventional way:

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

And there is the point-free way:

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

The preferred way should be the most readable one.


It's equivalent to

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


tags.map(&:name)

is The same as

tags.map{|tag| tag.name}

&:name just uses the symbol as the method name to be called.


While let us also note that ampersand #to_proc magic can work with any class, not just Symbol. Many Rubyists choose to define #to_proc on Array class:

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

# And then...

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

Ampersand & works by sending to_proc message on its operand, which, in the above code, is of Array class. And since I defined #to_proc method on Array, the line becomes:

[ 'Hello', 'Goodbye' ].map { |receiver| receiver.send( :+, ' world!' ) }


It's shorthand for tags.map { |tag| tag.name }.join(' ')


Two things are happening here, and it's important to understand both.

As described in other answers, the Symbol#to_proc method is being called.

But the reason to_proc is being called on the symbol is because it's being passed to map as a block argument. Placing & in front of an argument in a method call causes it to be passed this way. This is true for any Ruby method, not just map with symbols.

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)

The Symbol gets converted to a Proc because it's passed in as a block. We can show this by trying to pass a proc to .map without the 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"]

Even though it doesn't need to be converted, the method won't know how to use it because it expects a block argument. Passing it with & gives .map the block it expects.


Josh Lee's answer is almost correct except that the equivalent Ruby code should have been as follows.

class Symbol
  def to_proc
    Proc.new do |receiver|
      receiver.send self
    end
  end
end

not

class Symbol
  def to_proc
    Proc.new do |obj, *args|
      obj.send self, *args
    end
  end
end

With this code, when print [[1,'a'],[2,'b'],[3,'c']].map(&:first) is executed, Ruby splits the first input [1,'a'] into 1 and 'a' to give obj 1 and args* 'a' to cause an error as Fixnum object 1 does not have the method self (which is :first).


When [[1,'a'],[2,'b'],[3,'c']].map(&:first) is executed;

  1. :first is a Symbol object, so when &:first is given to a map method as a parameter, Symbol#to_proc is invoked.

  2. map sends call message to :first.to_proc with parameter [1,'a'], e.g., :first.to_proc.call([1,'a']) is executed.

  3. to_proc procedure in Symbol class sends a send message to an array object ([1,'a']) with parameter (:first), e.g., [1,'a'].send(:first) is executed.

  4. iterates over the rest of the elements in [[1,'a'],[2,'b'],[3,'c']] object.

This is the same as executing [[1,'a'],[2,'b'],[3,'c']].map(|e| e.first) expression.


(&:name) is short for (&:name.to_proc) it is same as tags.map{ |t| t.name }.join(' ')

to_proc is actually implemented in C


map(&:name) takes an enumerable object (tags in your case) and runs the name method for each element/tag, outputting each returned value from the method.

It is a shorthand for

array.map { |element| element.name }

which returns the array of element(tag) names


First, &:name is a shortcut for &:name.to_proc, where :name.to_proc returns a Proc (something that is similar, but not identical to a lambda) that when called with an object as (first) argument, calls the name method on that object.

Second, while & in def foo(&block) ... end converts a block passed to foo to a Proc, it does the opposite when applied to a Proc.

Thus, &:name.to_proc is a block that takes an object as argument and calls the name method on it, i. e. { |o| o.name }.


Although we have great answers already, looking through a perspective of a beginner I'd like to add the additional information:

What does map(&:name) mean in Ruby?

This means, that you are passing another method as parameter to the map function. (In reality you're passing a symbol that gets converted into a proc. But this isn't that important in this particular case).

What is important is that you have a method named name that will be used by the map method as an argument instead of the traditional block style.


It basically execute the method call tag.name on each tags in the array.

It is a simplified ruby shorthand.


There isn't a &: operator in Ruby. What you are seeing is the & operator applied to a :symbol.

In a method argument list, the & operator takes its operand, converts it to a Proc object if it isn't already (by calling to_proc on it) and passes it to the method as if a block had been used.

my_proc = Proc.new { puts "foo" }

my_method_call(&my_proc) # is identical to: my_method_call { puts "foo" }


Here :name is the symbol which point to the method name of tag object. When we pass &:name to map, it will treat name as a proc object. For short, tags.map(&:name) acts as:

tags.map do |tag|
  tag.name
end


it means

array.each(&:to_sym.to_proc)


It is same as below:

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

精彩评论

暂无评论...
验证码 换一张
取 消