Can you supply arguments to the map(&:method) syntax in Ruby?

Ruby

Ruby Problem Overview


You're probably familiar with the following Ruby shorthand (a is an array):

a.map(&:method)

For example, try the following in irb:

>> a=[:a, 'a', 1, 1.0]
=> [:a, "a", 1, 1.0]
>> a.map(&:class)
=> [Symbol, String, Fixnum, Float]

The syntax a.map(&:class) is a shorthand for a.map {|x| x.class}.

Read more about this syntax in "https://stackoverflow.com/questions/1217088/what-does-mapname-mean-in-ruby";.

Through the syntax &:class, you're making a method call class for each array element.

My question is: can you supply arguments to the method call? And if so, how?

For example, how do you convert the following syntax

a = [1,3,5,7,9]
a.map {|x| x + 2}

to the &: syntax?

I'm not suggesting that the &: syntax is better. I'm merely interested in the mechanics of using the &: syntax with arguments.

I assume you know that + is a method on Integer class. You can try the following in irb:

>> a=1
=> 1
>> a+(1)
=> 2
>> a.send(:+, 1)
=> 2

Ruby Solutions


Solution 1 - Ruby

You can create a simple patch on Symbol like this:

class Symbol
  def with(*args, &block)
    ->(caller, *rest) { caller.send(self, *rest, *args, &block) }
  end
end

Which will enable you to do not only this:

a = [1,3,5,7,9]
a.map(&:+.with(2))
# => [3, 5, 7, 9, 11] 

But also a lot of other cool stuff, like passing multiple parameters:

arr = ["abc", "babc", "great", "fruit"]
arr.map(&:center.with(20, '*'))
# => ["********abc*********", "********babc********", "*******great********", "*******fruit********"]
arr.map(&:[].with(1, 3))
# => ["bc", "abc", "rea", "rui"]
arr.map(&:[].with(/a(.*)/))
# => ["abc", "abc", "at", nil] 
arr.map(&:[].with(/a(.*)/, 1))
# => ["bc", "bc", "t", nil] 

And even work with inject, which passes two arguments to the block:

%w(abecd ab cd).inject(&:gsub.with('cde'))
# => "cdeeecde" 

Or something super cool as passing [shorthand] blocks to the shorthand block:

[['0', '1'], ['2', '3']].map(&:map.with(&:to_i))
# => [[0, 1], [2, 3]]
[%w(a b), %w(c d)].map(&:inject.with(&:+))
# => ["ab", "cd"] 
[(1..5), (6..10)].map(&:map.with(&:*.with(2)))
# => [[2, 4, 6, 8, 10], [12, 14, 16, 18, 20]] 

Here is a conversation I had with @ArupRakshit explaining it further:
Can you supply arguments to the map(&:method) syntax in Ruby?


As @amcaplan suggested in the comment below, you could create a shorter syntax, if you rename the with method to call. In this case, ruby has a built in shortcut for this special method .().

So you could use the above like this:

class Symbol
  def call(*args, &block)
    ->(caller, *rest) { caller.send(self, *rest, *args, &block) }
  end
end

a = [1,3,5,7,9]
a.map(&:+.(2))
# => [3, 5, 7, 9, 11] 

[(1..5), (6..10)].map(&:map.(&:*.(2)))
# => [[2, 4, 6, 8, 10], [12, 14, 16, 18, 20]] 

Here is a version using Refinements (which is less hacky than globally monkey patching Symbol):

module AmpWithArguments

  refine Symbol do
    def call(*args, &block)
      ->(caller, *rest) { caller.send(self, *rest, *args, &block) }
    end
  end

end

using AmpWithArguments

a = [1,3,5,7,9]
a.map(&:+.(2))
# => [3, 5, 7, 9, 11] 

[(1..5), (6..10)].map(&:map.(&:*.(2)))
# => [[2, 4, 6, 8, 10], [12, 14, 16, 18, 20]] 

Solution 2 - Ruby

For your example can be done a.map(&2.method(:+)).

Arup-iMac:$ pry
[1] pry(main)> a = [1,3,5,7,9]
=> [1, 3, 5, 7, 9]
[2] pry(main)> a.map(&2.method(:+))
=> [3, 5, 7, 9, 11]
[3] pry(main)> 

Here is how it works :-

[3] pry(main)> 2.method(:+)
=> #<Method: Fixnum#+>
[4] pry(main)> 2.method(:+).to_proc
=> #<Proc:0x000001030cb990 (lambda)>
[5] pry(main)> 2.method(:+).to_proc.call(1)
=> 3

2.method(:+) gives a Method object. Then &, on 2.method(:+), actually a call #to_proc method, which is making it a Proc object. Then follow What do you call the &: operator in Ruby?.

Solution 3 - Ruby

As the post you linked to confirms, a.map(&:class) is not a shorthand for a.map {|x| x.class} but for a.map(&:class.to_proc).

This means that to_proc is called on whatever follows the & operator.

So you could give it directly a Proc instead:

a.map(&(Proc.new {|x| x+2}))

I know that most probably this defeats the purpose of your question but I can't see any other way around it - it's not that you specify which method to be called, you just pass it something that responds to to_proc.

Solution 4 - Ruby

There is another native option for enumerables which is pretty only for two arguments in my opinion. the class Enumerable has the method with_object which then returns another Enumerable.

So you can call the & operator for a method with each item and the object as arguments.

Example:

a = [1,3,5,7,9]
a.to_enum.with_object(2).map(&:+) # => [3, 5, 7, 9, 11]

In the case you want more arguments you should repeat the proccess but it's ugly in my opinion:

a = [1,3,5,7,9]
a.to_enum.with_object(2).map(&:+).to_enum.with_object(5).map(&:+) # => [8, 10, 12, 14, 16]

Solution 5 - Ruby

Short answer: No.

Following @rkon's answer, you could also do this:

a = [1,3,5,7,9]
a.map &->(_) { _ + 2 } # => [3, 5, 7, 9, 11]

Solution 6 - Ruby

if all your method needs as argument is an element from the array, this is probably the simplest way to do it:

def double(x)
  x * 2
end

[1, 2, 3].map(&method(:double))

=> [2, 4, 6]

Solution 7 - Ruby

Instead of patching core classes yourself, as in the accepted answer, it's shorter and cleaner to use the functionality of the Facets gem:

require 'facets'
a = [1,3,5,7,9]
a.map &:+.(2)

Solution 8 - Ruby

I'm not sure about the Symbol#with already posted, I simplified it quite a bit and it works well:

class Symbol
  def with(*args, &block)
    lambda { |object| object.public_send(self, *args, &block) }
  end
end

(also uses public_send instead of send to prevent calling private methods, also caller is already used by ruby so this was confusing)

Solution 9 - Ruby

I'm surprised no one mentioned using curry yet, which has been in Ruby since Ruby 2.2.9. Here's how it can be done in the way OP wants using the standard Ruby library:

[1,3,5,7,9].map(&:+.to_proc.curry(2).call(11))
# => [12, 14, 16, 18, 20]

You need to supply an arity to curry that matches the call, though. This is because the interpreter doesn't know which object the + method refers to yet. This also means you can only use this when all the objects in map have the same arity. But that's probably not an issue if you're trying to use it this way.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionZack XuView Question on Stackoverflow
Solution 1 - RubyUri AgassiView Answer on Stackoverflow
Solution 2 - RubyArup RakshitView Answer on Stackoverflow
Solution 3 - RubyKostas RousisView Answer on Stackoverflow
Solution 4 - RubyPedro AugustoView Answer on Stackoverflow
Solution 5 - RubyAgisView Answer on Stackoverflow
Solution 6 - RubyCamilo SadView Answer on Stackoverflow
Solution 7 - RubymichauView Answer on Stackoverflow
Solution 8 - RubylocalhostdotdevView Answer on Stackoverflow
Solution 9 - RubyAndrewKSView Answer on Stackoverflow