Can the Ruby &: operator take arguments? [duplicate] - ruby

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]]
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]]

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.

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]

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]

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]

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)

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.

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)

Related

How to create a function in Ruby that take a average of an array?

I tried something like this:
def average_array_float(&array)
array.inject{ |sum, el| sum + el }.to_f / array.size
end
no success
array = [1, 2, 3]
def even_numbers(array)
array.select { |num| num.even? }
end
p array.even_numbers
reply:
$ bundle exec ruby main.rb
Traceback (most recent call last):
main.rb:7:in `<main>': private method `even_numbers' called for [1, 2, 3]:Array (NoMethodError)
exit status 1
what i am doing wrong?
You have to pass the array to the method:
def even_numbers(array)
array.select { |num| num.even? }
end
array = [1, 2, 3, 4, 5, 6]
even_numbers(array)
#=> [2, 4, 6]
The NoMethodError in your example happens because if you define a method on the top-level, it becomes a private method of Object:
Object.private_methods
#=> [:initialize, :inherited, :method_added, :method_removed, :method_undefined,
# :remove_const, :initialize_copy, :initialize_clone, :using, :public,
# :ruby2_keywords, :protected, :private, :included, :extended, :prepended,
# :even_numbers, :sprintf, :format, ...]
# ^^^^^^^^^^^^^
And since array is an Object, it can access that method (privately).
If you really wanted to add the method to Array, you could open the corresponding class:
class Array
def even_numbers
select { |num| num.even? }
end
end
Which gives you:
[1, 2, 3, 4, 5, 6].even_numbers
#=> [2, 4, 6]
However, although this works, it's not advised to alter objects that are not your own, let alone Ruby's core classes.
Regarding your other method, you could use sum and fdiv:
def average(array)
array.sum.fdiv(array.size)
end
average([1, 2, 4])
#=> 2.3333333333333335
Or quo if you prefer a precise result:
def average(array)
array.sum.quo(array.size)
end
average([1, 2, 4])
#=> (7/3)

How to pass arguments directy to a class without calling new method?

In Ruby a Hash can be created by:
Hash(a: 5, b: 6)
An Array can be created like this:
Array(100)
Sets can be created with the code:
require 'set'
Set[1,2,3]
So, how can I define a class that can accept arguments without calling the initialize method?
So, how can I define a class that can accept arguments without calling the initialize method?
You can't. In your examples, Hash and Array are actually methods.
And example with Set uses Set::[], naturally. And so it's not any different from any other class method that returns you instances of that class. For instance, User::create (or what-have-you).
In Ruby a Hash can be created by:
Hash(a: 5, b: 6)
Hash() is actually a method of the Kernel module:
p defined?(Hash()) # => "method"
p defined?(Kernel.Hash()) # => "method"
But without parentheses, Hash, Array, String, etc. all are just classes:
defined?(Hash) # => "constant"
defined?(Array) # => "constant"
In Ruby 2.6.3, the same goes for Arrays(), Complex(), Float(), Hash(), Integer(), Rational(), String(), and URI() - they all are methods.
But Set is a class:
require 'set'
p defined?(Set) # => "constant"
p set = Set[1,2,3] # => #<Set: {1, 2, 3}>
p set.to_a # => [1, 2, 3]
So, Set[1,2,3] is actually calling the [] method of Set. It looks kind of like this:
class Something
def initialize(*a)
#hash = {}
a.each { |v| #hash.store(v, nil) }
end
def self.[](*a) new(*a) end
define_method(:to_a) { #hash.keys }
define_method(:inspect) { "#<#{self.class}: {#{#hash.keys.to_s[1..-2]}}>" }
alias :to_s :inspect
end
p defined?(Something) # => "constant"
p set = Something[1,2,3] # => #<Something: {1, 2, 3}>
p set1 = Something[[1, 2, 3], 2, 2, 3, 4, {4 => :a}, 5] # => #<Something: {[1, 2, 3], 2, 3, 4, {4=>:a}, 5}>
p set.to_a # => [1, 2, 3]
p set1.to_a # => [[1, 2, 3], 2, 3, 4, [4, 4], 5]
Back to the question:
So, how can I define a class that can accept arguments without calling
the initialize method?
I don't think it's possible!

Implement to_s(2) with String#to_proc in Ruby

I'm learning about the unary operator, &.
There are some great questions about using & in the parameters of a method invocation. Usually the format goes something like some_obj.some_method(&:symbol):
Ruby unary operator & only valid on method arguments
What is the functionality of “&: ” operator in ruby?
What does map(&:name) mean in Ruby?
Unary Ampersand Operator and passing procs as arguments in Ruby
It seems like the main idea is ruby calls the to_proc method on :symbol when the unary operator is placed in front of the symbol. Because Symbol#to_proc exists "everything works".
I'm still confused about how everything just works.
What if I want to implement a "to_proc sort of functionality with a string". I'm putting it in quotes because I'm not really sure how to even talk about what I'm trying to do.
But the goal is to write a String#to_proc method such that the following works:
class String
def to_proc # some args?
Proc.new do
# some code?
end
end
end
p result = [2, 4, 6, 8].map(&'to_s 2')
#=> ["10", "100", "110", "1000"]
This is how I did it:
class String
def to_proc
Proc.new do |some_arg|
parts = self.split(/ /)
some_proc = parts.first.to_sym.to_proc
another_arg = parts.last.to_i
some_proc.call(some_arg, another_arg)
end
end
end
p result = [2, 4, 6, 8].map(&'to_s 2')
#=> ["10", "100", "110", "1000"]
The main part I'm confused about is how I get the parameters into the String#to_proc method. It seems like:
def to_proc
Proc.new do |some_arg| ...
end
Should be:
def to_proc some_arg
Proc.new do |yet_another_arg| ...
end
Or something like that. How do the [2, 4, 6, 8] values get into the proc that String#to_proc returns?
Just write this
[2, 4, 6, 8].map { |each| each.to_s(2) }
Though I guess that is not what you're looking for …
Here is how Symbol#to_proc is implemented.
class Symbol
def to_proc
proc { |each| each.send(self) }
end
end
If you want you can define to_proc on an Array as follows
class Array
def to_proc
symbol, *args = self
proc { |each| each.send(symbol, *args) }
end
end
And then use
[2, 4, 6, 8].map(&[:to_s, 2])
Another alternative is using curry.
Though that does not work with bound methods, so you'll have to define a to_s lambda function first.
to_s = lambda { |n, each| each.to_s(n) }
[2, 4, 6, 8].map(&to_s.curry[2])
Though all of that seems more like academic exercises.
When you run some_method(&some_obj), Ruby first call the some_obj.to_proc to get a proc, then it "converts" that proc to a block and passes that block to some_method. So how the arguments go into the proc depends on how some_method passes arguments to the block.
For example, as you defined String#to_proc, which returns a proc{|arg| ...} (a proc with one argument), and calls [...].map(&'to_s 2'), Ruby interprets it as
[...].map(&('to_s 2'.to_proc))
which is
[...].map(&proc{|arg| ... })
and finally
[...].map {|arg| ... }
The problem with your approach is that there's no way to deduce the type of the argument when it's always passed as a string.
By the way, to address your question:
How do the [2, 4, 6, 8] values get into the proc that String#to_proc returns?
They are some_arg here, which is not a variable you have to define but instead is a parameter that is automatically passed when the proc is called.
Here's a rewriting of the String patch and some usage examples:
class String
def to_proc
fn, *args = split ' '
->(obj) { obj.send(fn.to_sym, *args) }
end
end
This works for the following example:
p result = [[1,2,3]].map(&"join -")
# => ['1-2-3']
but fails for this (your example):
p result = [2, 4, 6, 8].map(&'to_s 2')
# => TypeError
The problem is to_s('2') is being called, when the 2 should be an integer, not a string. I can't think of any way to get around this except for maybe some serialization (although one of the other answers shows how eval can work).
Now that the limitations of this approach are clear, it's worth comparing it to the more commonly used patch on Symbol to enable argument passing to proc shorthands (this taken from can-you-supply-arguments-to-the-mapmethod-syntax-in-ruby)
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]
This way you can pass any type of arguments to the proc, not just strings.
Once you've defined it, you can easily swap out String for Symbol:
class String
def call(*args, &blk)
to_sym.call(*args, &blk)
end
end
puts [1,2,3].map(&'+'.(1))
Refactored code
You're free to choose the name for the proc block variable. So it could be yet_another_arg, some_arg or something_else. In this case, the object you're passing to to_proc is actually the object you want to receive the proc call, so you could call it receiver. The method and param are in the String, so you get them with String#split from self.
class String
def to_proc
proc do |receiver|
method_name, param = self.split
receiver.method(method_name.to_sym).call(param.to_i)
end
end
end
p result = [2, 4, 6, 8].map(&'to_s 2')
# => ["10", "100", "110", "1000"]
Note that this method has been tailored to accept one method name and one integer argument. It doesn't work in the general case.
Another possibility
Warning
eval is evil
You've been warned
This works with the exact syntax you wanted, and it also works for a wider range of methods and parameters :
class String
def to_proc
proc { |x| eval "#{x.inspect}.#{self}" }
end
end
p [2, 4, 6, 8].map(&'to_s 2')
#=> ["10", "100", "110", "1000"]
p ["10", "100", "110", "1000"].map(&'to_i 2')
#=> [2, 4, 6, 8]
p [1, 2, 3, 4].map(&'odd?')
#=> [true, false, true, false]
p %w(a b c).map(&'*3')
#=> ["aaa", "bbb", "ccc"]
p [[1,2,3],[1,2],[1]].map(&'map(&"*2")')
#=> [[2, 4, 6], [2, 4], [2]]
It also brings security problems, though. With great power comes great responsibility!

What is `&method` called? How to pass multiple arguments in it? [duplicate]

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]]
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]]
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.
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]
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]
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]
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)
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.
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)

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

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]]
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]]
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.
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]
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]
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]
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)
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.
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)

Resources