63

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.

BinaryButterfly
  • 18,137
  • 13
  • 50
  • 91
swapnesh
  • 26,318
  • 22
  • 94
  • 126
  • The other answers are great for answering how `to_proc` can be used in practice. However I found these simple documentation links a better answer for "what is it", including "what is a `Proc`", which is what `to_proc` returns. https://apidock.com/rails/Symbol/to_proc https://ruby-doc.org/core-2.2.0/Proc.html – Richard Aug 25 '18 at 09:39

4 Answers4

123

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}
user664833
  • 18,397
  • 19
  • 91
  • 140
sawa
  • 165,429
  • 45
  • 277
  • 381
  • Plus, according to this , it's 20 times faster during runtime. – Bob. May 22 '15 at 13:23
  • 1
    I understand `&proc` gives a block, `&x` results in x becoming proc then whole thing also gives a block. I also understand that Symbol has to_proc method. However the part i dont understand and i feel this answer lacks, is how symbol and methods are connected. i mean it's not like all methods are also available by the symbol names – Muhammad Umer Feb 28 '16 at 09:35
  • 2
    @MuhammadUmer You can call a method on an object like `1.to_s` and `1.send(:to_s)`. So really `(1..10).each(&:to_s)` is equivalent to `(1..10).each { |x| x.send(:to_s) }`. The symbol is passed as an argument to the `send()` method. Look at this [link](http://stackoverflow.com/a/9932720/4812050). – Cruz Nunez Jan 25 '17 at 21:45
  • @MuhammadUmer In other words, yes, it is indeed like all methods are also available by the symbol names. That's how Ruby stores method names internally. – BobRodes Feb 14 '20 at 10:18
49

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

Dillon Benson
  • 4,280
  • 4
  • 23
  • 25
  • 1
    just wondering if there is a typo in your answer here. Did you mean on line 2 to say '... #succ is a method defined by class Integer ...'? (V helpful answer for my proc learning exercise anyway, thank you.) – jbk Mar 13 '23 at 14:41
8

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'
Keith Bennett
  • 4,722
  • 1
  • 25
  • 35
  • This isn't entirely accurate, because it creates a lambda, while the original `Symbol#to_proc` does not. – BobRodes Feb 14 '20 at 09:20
  • @BobRodes Interesting, I did not think to check that. So it should be `Proc.new { |object| object.send(self) }`? – Keith Bennett Feb 14 '20 at 09:40
  • 1
    I had that feeling, too, and tried it. I'd say it's closer, but when I ran it in `irb`, the monkey-patched version of `:foo.to_proc` gave me this: `#`, while the original gave me this: `#`. I tried running the patch from a file called `test.rb` and got this: `#`. Apparently, in the patch version, `self` refers to the main environment rather than the symbol, so there must be more to it. – BobRodes Feb 14 '20 at 09:58
  • I just ran a test on it, though, and it seems to work ok. Test is this method (have to make it public specifically for reasons that aren't fully clear to me): `public; def foo; "Hi, I'm foo."; end`, and this call: `p [''].map(&:foo)`. Works with the original and the monkey-patch. – BobRodes Feb 14 '20 at 10:10
2

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.

Gerry
  • 10,584
  • 4
  • 41
  • 49