Search code examples
rubyproc

In Ruby, how do you write a simple method that can be used with &:symbol?


This article touches on the issues but doesn't give a solution.

This started when I wanted to write a method and optionally pass it an argument which could be null or a ???? (proc, lambda, method, block, ???). Lets call it, for now, a block because a block works. The block takes one required argument. An example of the method and a call to it would be:

#!/usr/bin/env ruby

def foo(&proc)
  puts "before"
  if proc
    yield "passed to proc"
  end
  puts "after"
end

def add_message(s)
  puts "from add_message #{s}"
end

foo { |s| add_message(s) }
foo

And the output is:

before
from add_message passed to proc
after
before
after

Great. But, what I'd like to do is be able to call foo like this: foo(&:add_message). But I can't. Changing line 15 above I get:

before
./temp.rb:11:in `add_message': wrong number of arguments (given 0, expected 1) (ArgumentError)
    from ./temp.rb:6:in `foo'
    from ./temp.rb:15:in `<main>'

And, as the article above mentions, the arity is now -2. So, how do I write a simple method like add_message that I can use with &:add_message. OR!!! as is the case 99.99% of the time, please set me on the proper track on how to do this.


Solution

  • 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