Reputation: 2349
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
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
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
, andHash
.
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
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