pedz
pedz

Reputation: 2349

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.

Upvotes: 3

Views: 232

Answers (3)

Alex
Alex

Reputation: 30131

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

Upvotes: 4

Silvio Mayolo
Silvio Mayolo

Reputation: 70407

From the Ruby docs

Conversion of other objects to procs

Any object that implements the to_proc method can be converted into a proc by the & operator, and therefore can be consumed by iterators.

class Greeter
  def initialize(greeting)
    @greeting = greeting
  end

  def to_proc
    proc {|name| "#{@greeting}, #{name}!" }
  end
end

hi = Greeter.new("Hi")
hey = Greeter.new("Hey")
["Bob", "Jane"].map(&hi)    #=> ["Hi, Bob!", "Hi, Jane!"]
["Bob", "Jane"].map(&hey)   #=> ["Hey, Bob!", "Hey, Jane!"]

Of the Ruby core classes, this method is implemented by Symbol, Method, and Hash.

So when you pass an argument with a unary ampersand before it, to_proc gets called on it. The &: "syntax" is actually & being called on a symbol literal, i.e. &(:foobar), and Symbol.to_proc has the behavior of converting a symbol into a method call on its first argument, i.e. these two are roughly equivalent (modulo named argument forwarding)

:foobar.to_proc
proc { |x, *args| x.foobar(*args) }

Ruby's Method type also implements to_proc, so if you have a standalone method called foobar (on a module, say, Example), then you can call Example.method(:foobar) and get an &-compatible object. If you have a "top-level" method, then it's probably being defined on the main object and calling method with no explicit receiver will work.

The other type mentioned in that quote is hashes, which can be turned into a function mapping their keys to their values (and returning nil if no matching key exists). And, of course, you can always implement a method called to_proc on your own classes and it'll work just as well as any built-in type.

Upvotes: 3

MoRe
MoRe

Reputation: 2372

class Integer
    def set
        return self + 1
    end
end

p [1,2,3,4,5,6].map(&:set)

I think when you can use &: syntax that a method have been defined for a class like above

Upvotes: 1

Related Questions