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.
CodePudding user response:
The problem is arity.
# `yield` will pass it's arguments to proc
>> :add_message.to_proc.call('passed to proc')
# => ArgumentError
The solution is to make a proc that can accept the same arguments as add_message
method. We can use Object#method
that returns Method
object that implements it's 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
CodePudding user response:
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.
CodePudding user response:
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