What does to_proc method mean in Ruby? - ruby

I am learning rails and following this thread. I am stuck with the to_proc method. I consider symbols only as alternatives to strings (they are like strings but cheaper in terms of memory). If there is anything else I am missing for symbols, then please tell me. Please explain in a simple way what to_proc means and what it is used for.

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}

The easiest way to explain this is with some examples.
(1..3).collect(&:to_s) #=> ["1", "2", "3"]
Is the same as:
(1..3).collect {|num| num.to_s} #=> ["1", "2", "3"]
and
[1,2,3].collect(&:succ) #=> [2, 3, 4]
Is the same as:
[1,2,3].collect {|num| num.succ} #=> [2, 3, 4]
to_proc returns a Proc object which responds to the given method by symbol.
So in the third case, the array [1,2,3] calls its collect method and. succ is method defined by class Array. So this parameter is a short hand way of saying collect each element in the array and return its successor and from that create a new array which results in [2,3,4]. The symbol :succ is being converted to a Proc object so it call the Array's succ method.

For me the clearest explanation is seeing a simple implementation of it. Here's what it might look like if I were reimplementing Symbol#to_proc:
class Symbol # reopen Symbol class to reimplement to_proc method
def to_proc
->(object) { object.send(self) }
end
end
my_lambda = :to_s.to_proc
puts my_lambda.(1) # prints '1'; .() does the same thing as .call()
puts my_lambda.(1).class # prints 'String'
puts [4,5,6].map(&:to_s) # prints "4\n5\n6\n"
puts [4,5,6].map(&:to_s).first.class # prints 'String'

For anybody still a bit stumped, running the following code might make things a little clearer:
class Symbol
def to_proc
proc do |obj|
puts "Symbol proc: #{obj}.send(:#{self})"
obj.send(self)
end
end
end
class Array
def map(&block)
copy = self.class.new
self.each do |index|
puts "Array.map: copy << block.call(#{index})"
copy << block.call(index)
end
copy
end
end
remapped_array = [0, 1, 2].map &:to_s
puts "remapped array: #{remapped_array.inspect}"
These are not the actual implementations of Symbol.to_proc or Array.map, they are just simplified versions which I'm using to demonstrate how map &:to_s and similar calls work.

Related

When to use (:method) or (&:method)

Why does: respond_to? in:
class Wolf
def howl; end
end
Wolf.new.respond_to?(:howl) # => true
not require & while map in:
["1", "2", "3"].map(&:to_i) # => [1, 2, 3]
does? Also, are there any technical names for this?
When you say :method, you're using some nice syntactical sugar in ruby that creates a new Symbol object. When you throw an ampersand before it (&:method), you're using another piece of sugar. This invokes the to_proc method on the symbol.
So, these two things are identical:
method_proc = &:method
sym = :method
method_proc = method.to_proc
What's the difference between that and the other usage? Well, respond_to? has a single argument -- a symbol. So we can pass :method and be all fine and dandy. (Interestingly, objects do respond to the method named method, but that's a far more confusing question).
By comparison, Enumerable's iterators (like map, select, etc) accept a block. When we pass a Proc, it is interpreted properly as that block. So, these two pieces of code are equivalent:
[1,2,3].map { |i| i.even? }
[1,2,3].map(&:even?)
This equivalence is a little confusing, because of course Symbol has no idea that there's an even? method somewhere. To play around with it, I used evenproc = :even?.to_proc to inspect the resulting proc. It's implemented in C (at least in MRI ruby), and isn't willing to give up its source. However, its arity is -1, which means that it accepts one optional arg. My best guess is that it does something like this:
def to_proc
method_name = self.to_s
->(a) { a.send(method_name) }
end
I could dig further, but I think we've already gone way past the question. ;) Good luck!

Why do some methods work with inject and some won't?

I understand that I can pass a method to inject. For instance,
[1,2,3].inject(:+) #=> 6
but this one throws
["1","2","3"].inject(:to_i) #=> TypeError: no implicit conversion of String into Integer
["1","2","3"].inject(:to_s) #=> ArgumentError: wrong number of arguments (1 for 0)
I am not doing anything particular, just trying to get my basics right.
The short explanation is "the callback for inject has to take two arguments." But that probably won't clear it up entirely.
OK, so let's look at the normal block form of inject:
[1, 2, 3].inject {|memo, number| memo + number}
Passing a symbol works the same way — it just turns the symbol into a proc that takes the place of the block. When you turn a symbol into a proc, the conversion looks like this:
class Symbol
def to_proc
proc {|receiver, *args| receiver.send(self, *args) }
end
end
So when you pass :+, it calls the + method of the memo value with the current number as the argument, just like 1 + 2.
So when you pass :to_i, it's equivalent to this:
["1", "2", "3"].inject {|memo, number_string| memo.to_i(number_string) }
But that doesn't make any sense. You're trying to pass the string as an argument to to_i. which is invalid.

odd usage of "end" in Sample code

Looking through this I notice something I have never seen before on line 83.end.map(&:chomp) so end is an object? (I realize that might be 100% wrong.) Can someone explain what and how that works there? What exactly is advantage?
No, end is not an object, but object.some_method do ... end is an object (or rather it's evaluated to an object) - namely the object returned by the some_method method.
So if you do object.some_method do ... end.some_other_method, you're calling some_other_method on the object returned by some_method.
The full code snippet you're referring to is below:
def initialize(dict_file)
#dict_arr = File.readlines(dict_file).select do |word|
!word.include?("-") && !word.include?("'")
end.map(&:chomp)
end
notice that the end you're talking about is the end of the block that starts on the 2nd line (it matches the do on line 2).
Perhaps if you see it parenthesized, and rewritten with curly braces, it will make more sense:
def initialize(dict_file)
#dict_arr = (File.readlines(dict_file).select { |word|
!word.include?("-") && !word.include?("'")
}).map(&:chomp)
end
It's often helpful to examine what Ruby is doing, step-by-step. Let's see what's going with the method ComputerPlayer#initialize:
def initialize(dict_file)
#dict_arr = File.readlines(dict_file).select do |word|
!word.include?("-") && !word.include?("'")
end.map(&:chomp)
end
First, create a file:
File.write("my_file", "cat\ndog's\n")
When we execute:
ComputerPlayer.new("my_file")
the class method IO#readlines is sent to File, which returns an array a:
a = File.readlines("my_file")
#=> ["cat\n", "dog's\n"]
Enumerable#select is sent to the array a to create an enumerator:
b = a.select
#=> #<Enumerator: ["cat\n", "dog's\n"]:select>
We can convert this enumerator to an array to see what it will pass to it's block:
b.to_a
=> ["cat\n", "dog's\n"]
The enumerator is invoked by sending it the method each with a block, and it returns an array c:
c = b.each { |word| !word.include?("-") && !word.include?("'") }
#=> ["cat\n"]
Lastly, we send Enumerable#map with argument &:chomp (the method String#chomp converted to a proc) to the array c:
c.map(&:chomp)
#=> ["cat"]
A final point: you can improve clarity by minimizing the use of !. For example, instead of
...select do |word|
!word.include?("-") && !word.include?("'")
consider
...reject do |word|
word.include?("-") || word.include?("'")
You might also use a regex.

Accessing a passed block in Ruby

I have a method that accepts a block, lets call it outer. It in turn calls a method that accepts another block, call it inner.
What I would like to have happen is for outer to call inner, passing it a new block which calls the first block.
Here's a concrete example:
class Array
def delete_if_index
self.each_with_index { |element, i| ** A function that removes the element from the array if the block passed to delete_if_index is true }
end
end
['a','b','c','d'].delete_if_index { |i| i.even? }
=> ['b','d']
the block passed to delete_if_index is called by the block passed to each_with_index.
Is this possible in Ruby, and, more broadly, how much access do we have to the block within the function that receives it?
You can wrap a block in another block:
def outer(&block)
if some_condition_is_true
wrapper = lambda {
p 'Do something crazy in this wrapper'
block.call # original block
}
inner(&wrapper)
else
inner(&passed_block)
end
end
def inner(&block)
p 'inner called'
yield
end
outer do
p 'inside block'
sleep 1
end
I'd say opening up an existing block and changing its contents is Doing it WrongTM, maybe continuation-passing would help here? I'd also be wary of passing around blocks with side-effects; I try and keep lambdas deterministic and have actions like deleting stuff in the method body. In a complex application this will likely make debugging a lot easier.
Maybe the example is poorly chosen, but your concrete example is the same as:
[1,2,3,4].reject &:even?
Opening up and modifying a block strikes me as code smell. It'd be difficult to write it in a way that makes the side effects obvious.
Given your example, I think a combination of higher order functions will do what you're looking to solve.
Update: It's not the same, as pointed out in the comments. [1,2,3,4].reject(&:even?) looks at the contents, not the index (and returns [1,3], not [2,4] as it would in the question). The one below is equivalent to the original example, but isn't vary pretty.
[1,2,3,4].each_with_index.reject {|element, index| index.even? }.map(&:first)
So here's a solution to my own question. The passed in block is implicitly converted into a proc which can be received with the & parameter syntax. The proc then exists inside the closure of any nested block, as it is assigned to a local variable in scope, and can be called by it:
class Array
def delete_if_index(&proc)
ary = []
self.each_with_index { |a, i| ary << self[i] unless proc.call(i) }
ary
end
end
[0,1,2,3,4,5,6,7,8,9,10].delete_if_index {|index| index.even?}
=> [1, 3, 5, 7, 9]
Here the block is converted into a proc, and assigned to the variable proc, which is then available within the block passed to each_with_index.

Ruby: How to chain multiple method calls together with "send"

There has to be a built in way of doing this, right?
class Object
def send_chain(arr)
o=self
arr.each{|a| o=o.send(a) }
return o
end
end
I just ran across this and it really begs for inject:
def send_chain(arr)
arr.inject(self) {|o, a| o.send(a) }
end
Building upon previous answers, in case you need to pass arguments to each method, you can use this:
def send_chain(arr)
Array(arr).inject(self) { |o, a| o.send(*a) }
end
You can then use the method like this:
arr = [:to_i, [:+, 4], :to_s, [:*, 3]]
'1'.send_chain(arr) # => "555"
This method accepts single arguments as well.
No, there isn't a built in way to do this. What you did is simple and concise enough, not to mention dangerous. Be careful when using it.
On another thought, this can be extended to accept arguments as well:
class Object
def send_chain(*args)
o=self
args.each do |arg|
case arg
when Symbol, String
o = o.send arg # send single symbol or string without arguments
when Array
o = o.send *arg # smash the inner array into method name + arguments
else
raise ArgumentError
end
end
return o
end
end
this would let you pass a method name with its arguments in an array, like;
test = MyObject.new
test.send_chain :a_method, [:a_method_with_args, an_argument, another_argument], :another_method
How about this versatile solution without polluting the Object class:
def chain_try(arr)
[arr].flatten.inject(self_or_instance, :try)
end
or
def chain_send(arr)
[arr].flatten.inject(self_or_instance, :send)
end
This way it can take a Symbol, a String or an Array with a mix of both even.🤔
example usage:
chain_send([:method1, 'method2', :method3])
chain_send(:one_method)
chain_send('one_method')

Resources