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 \"What does map(&:name) 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
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]]
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?.
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
.
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]
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)
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 & 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]