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

Time:05-01

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

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

  • Related