Symbol#To_Proc with Custom Methods

Symbol#to_proc with custom methods

class Integer
def times_three
return self * 3
end
end

Now, because times_three is now a method of the Integer class, you can do symbol to proc...

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

If you want to access a method that isn't part of the object's class but acts on an object, you need to pass the object as an argument to the method...

def times_three(x)
x * 3
end

[1, 2, 3].map{|i| times_three(i) }

the symbol to proc needs to use the object as a receiver.

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

is equivalent to

[1, 2, 3].map{|i| i.some_action}

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}

Using Symbol#to_proc with multiple procs

If you are looking for a good architectural solution then you need to implement FIFO stack for that.

Or you can do it like you mentioned above:

Object.tap(&:do_work).tap(&:do_more_work)

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.

Implementing &:method with custom methods

It seems you are expected to extend Array class with new method accumulate which will accumulate the results of invoking a given proc on each element of the array.

One implementation can be like this:

class Array
def accumulate(&block)
self.collect { |i| block.yield(i) }
end
end

p result = %w(hello world).accumulate(&:upcase) # Prints ["HELLO", "WORLD"]
p result = %w(hello world).accumulate { |b| b.upcase } # Prints ["HELLO", "WORLD"]

Please note that %w(HELLO WORLD) is same as array ["HELLO", "WORLD"]

There is a very good explanation of what is the use of & operator in this article - please read the section on The Unary &

Symbol#to_proc shorthand with the stabby lambda syntax

Apparently, it is not supported.

I think it has something to do with the fact that proc and lambda are actually methods, and not keywords. They support the same features that we usually associate with each and the other methods from the Enumerable module. However, -> is a special language construct which is parsed separately.

I can't think of a reason why something like -> &:method shouldn't be possible, but as of now the syntax of the Ruby language simply doesn't allow it.

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| ... }

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

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)

How to define a method to_proc in Array so I can use it in a particular situations

The standard Symbol#to_proc is, more or less, a e.send(self) call and you just want to send an array full of symbols to each element so just say exactly that with something like this:

class Array
def to_proc
proc { |e| self.map { |m| e.send(m) } }
end
end

I'd probably not patch Array for this, I'd just use a local lambda if I wanted something easier to read:

brand_and_color = lambda { |e| [:brand, :color].map { |s| e.send(s) } }
array_of_cars.map(&brand_and_color)


Related Topics



Leave a reply



Submit