What Does To_Proc Method Mean in Ruby

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



Leave a reply



Submit