What does to_proc method mean in Ruby?
Some methods take a block, and this pattern frequently appears for a block:
{|x| x.foo}
and people would like to write that in a more concise way. In order to do that they use a combination of: a symbol, the method Symbol#to_proc
, implicit class casting, and &
operator. If you put &
in front of a Proc
instance in the argument position, that will be interpreted as a block. If you combine something other than a Proc
instance with &
, then implicit class casting will try to convert that to a Proc
instance using to_proc
method defined on that object if there is any. In case of a Symbol
instance, to_proc
works in this way:
:foo.to_proc # => ->x{x.foo}
For example, suppose you write:
bar(&:foo)
The &
operator is combined with :foo
, which is not a Proc
instance, so implicit class cast applies Symbol#to_proc
to it, which gives ->x{x.foo}
. The &
now applies to this and is interpreted as a block, which gives:
bar{|x| x.foo}
How does Ampersand pass arguments into #to_proc as obj? —receiving_method(&:method)
How does Ampersand pass arguments into
#to_proc
as“obj”
? —receiving_method(&:method)
Ampersand does not pass anything anywhere. Ampersand converts the argument to a proc
instance, implicitly calling to_proc
on it. And passes the result as a block to the caller.
Let’s stick with the example:
%w[42 foo].map(&:to_i)
#⇒ [42, 0]
What’s going on here?
• to_i
is being converted to proc
as you shown in the OP
# ⇓⇓⇓⇓⇓
proc { |obj, *args| obj.send(:to_i, *args) }
• we pass this proc
to the caller (without lose of generality, I’d write it with block syntax for the sake of clarity
%w[42 foo].map do |obj, *args|
obj.send(:to_i, *args)
end
• NB! *args
is off-side here, since map
passes the single argument to the block:
%w[42 foo].map do |obj|
obj.send(:to_i)
end
That would map:
'42' → '42'.send(:to_i) ≡ '42'.to_i → 42,
'foo' → 'foo'.send(:to_i) ≡ 'foo'.to_i → 0,
yielding:
#⇒ [42, 0]
Implement to_s(2) with String#to_proc in Ruby
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| ... }
Why does Ruby only allow to refer to methods by symbols when calling to_proc on them?
Given the following valid Ruby example:
"to_i".to_sym.to_proc.call("1")
To call #to_proc
on a Symbol creates a proc that receive one parameter: the object that should receive an invocation of the method named by the symbol. Something like this:
->(object) {
object.send(:to_i)
}
What the &
do is to pass the block returned by #to_proc
as a proper invocation block to the method being called. When used with Enumerable#map
, the final result is that the passed
block will be invoked for each element on the collection and receive
the current iteration element being passed as parameter to the block.
So the ["1", "2"].map(&:to_i)
syntax is a shortcut to something like
this:
block = ->(el) {
el.send(:to_i)
}
["1", "2"].map &block
About your examples:
When you try to call &to_i
in your example, Ruby will try to invoke
a variable or method named to_i
. Since it doesn't exists in the
scope (it is a method of each String
in the Array
, not in the "global" scope), it'll generate an error.
About the other example: "to_i".to_sym.to_proc
will transform the:to_i
into a proc that when invoked with one parameter, will try to
call the method named after the symbol (:to_i
) against the target
parameter. This means that you could use the &
to transform the proc
into a "invocation block":
["1", "2"].map(&"to_i".to_sym.to_proc)
what does this syntax mean in Ruby? .. tasks.all?(&:complete?)
&
is for passing block to method as a block (also used the other way in parameter list to make implicit block a parameter), it implicitly calls to_proc
on passed object.
Symbol#to_proc
for :symbol
makes a proc{|param| param.symbol }
So your code is equvalent to tasks.all?{|task| task.complete? }
Chaining & to_proc on symbol
If you're only doing:
%i[a b c].map { |e| e.to_s.upcase }
then just use the block and get on with more important things. If you're really doing a chain of Enumerable calls and find the blocks too visually noisy:
%i[a b c].map { |e| e.to_s.upcase }.some_chain_of_enumerable_calls...
then you could toss your logic into a lambda to help clean up the appearance:
to_s_upcase = lambda { |e| e.to_s.upcase }
%i[a b c].map(&to_s_upcase).some_chain_of_enumerable_calls...
or throw it in a method and say:
%i[a b c].map(&method(:to_s_upcase)).some_chain_of_enumerable_calls...
Either way, you're giving your little bit of logic a name (which is pretty much all &:symbol
is doing for you) to make the code more readable and easier to understand. In the specific case of to_s.upcase
, this is all a bit pointless but these approaches are quite useful when the block gets bigger.
Ruby Proc Syntax
When you prefix the last argument of a call with &
you are making clear that you are sending a block and not a normal argument. Ok, in method(&:something)
, :something
is a symbol, not a proc, so Ruby automatically calls the method to_proc
to get a real block. And Rails guys (and now also vanilla Ruby) cleverly defined it as:
class Symbol
def to_proc
proc { |obj, *args| obj.send(self, *args) }
end
end
That's why you can do:
>> [1, 2, 3].map(&:to_s) # instead of [1, 2, 3].map { |n| n.to_s }
=> ["1", "2", "3"]
[edit] Note: when you realize that this construction is no syntatic sugar but generic infrastructure that Ruby provides, nothing stops you from implementing your own to_proc
for other classes. Never felt limited because &:method
allowed no arguments?
class Array
def to_proc
proc { |obj, *args| obj.send(*(self + args)) }
end
end
>> ["1", "F", "FF"].map(&[:to_i, 16])
=> [1, 15, 255]
What are :+ and &:+ in Ruby?
Let's start with an easier example.
Say we have an array of strings we want to have in caps:
['foo', 'bar', 'blah'].map { |e| e.upcase }
# => ['FOO', 'BAR', 'BLAH']
Also, you can create so called Proc objects (closures):
block = proc { |e| e.upcase }
block.call("foo") # => "FOO"
You can pass such a proc to a method with the & syntax:
block = proc { |e| e.upcase }
['foo', 'bar', 'blah'].map(&block)
# => ['FOO', 'BAR', 'BLAH']
What this does, is call to_proc on block and then calls that for every block:
some_object = Object.new
def some_object.to_proc
proc { |e| e.upcase }
end
['foo', 'bar', 'blah'].map(&some_object)
# => ['FOO', 'BAR', 'BLAH']
Now, Rails first added the to_proc method to Symbol, which later has been added to the ruby core library:
:whatever.to_proc # => proc { |e| e.whatever }
Therefore you can do this:
['foo', 'bar', 'blah'].map(&:upcase)
# => ['FOO', 'BAR', 'BLAH']
Also, Symbol#to_proc is even smarter, as it actually does the following:
:whatever.to_proc # => proc { |obj, *args| obj.send(:whatever, *args) }
This means that
[1, 2, 3].inject(&:+)
equals
[1, 2, 3].inject { |a, b| a + b }
In Ruby, how do you write a simple method that can be used with &:symbol?
The problem is that Symbol#to_proc
does not create a proc that calls add_message
method correctly.
# `yield` will pass its arguments to proc
>> :add_message.to_proc.call('passed to proc')
# => ArgumentError
This calls 'passed to proc'.add_message
, because our method is defined in Object
it works when called on String
, however it is missing the required argument.
The solution is to make a proc that can accept the same arguments as add_message
method and pass them along to that method. We can use Object#method
that returns Method
object that implements its own to_proc
and has the same arity as the method.
>> method(:add_message).to_proc.arity
=> 1
>> method(:add_message).to_proc.call('passed to proc')
from add_message passed to proc
>> foo(&method(:add_message))
before
from add_message passed to proc
after
Related Topics
Gem Installation Error: You Have to Install Development Tools First (Windows)
Best Way to Escape and Unescape Strings in Ruby
How to Convert a Ruby Hash Object to Json
Add a Default Value to a Column Through a Migration
Rails Keeps Telling Me That It's Not Currently Installed
Rhc Setup Gives Error 'No Such File Dl/Import'
How to Get a Specific Output Iterating a Hash in Ruby
How to Count Duplicate Elements in a Ruby Array
Difference Between '..' (Double-Dot) and '...' (Triple-Dot) in Range Generation
What Does 'Monkey Patching' Exactly Mean in Ruby
How to Get a Single Character Without Pressing Enter
Custom Authentication Strategy For Devise
Access Variables Programmatically by Name in Ruby
How to Pick Randomly from an Array
How to Run a Rake Task from Capistrano