Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I enforce arity on a block passed to a method?

Tags:

ruby

lambda

proc

Is there any way to "turn on" the strict arity enforcement of a Proc instantiated using Proc.new or Kernel.proc, so that it behaves like a Proc instantiated with lambda?

My initialize method takes a block &action and assigns it to an instance variable. I want action to strictly enforce arity, so when I apply arguments to it later on, it raises an ArgumentError that I can rescue and raise a more meaningful exception. Basically:

class Command
  attr_reader :name, :action

  def initialize(name, &action)
    @name   = name
    @action = action
  end

  def perform(*args)
    begin
      action.call(*args)
    rescue ArgumentError
      raise(WrongArity.new(args.size))
    end
  end
end

class WrongArity < StandardError; end

Unfortunately, action does not enforce arity by default:

c = Command.new('second_argument') { |_, y| y }
c.perform(1) # => nil

action.to_proc doesn't work, nor does lambda(&action).

Any other ideas? Or better approaches to the problem?

Thanks!

like image 704
rickyrickyrice Avatar asked Nov 29 '12 07:11

rickyrickyrice


People also ask

What is the difference between procs and blocks?

When using parameters prefixed with ampersands, passing a block to a method results in a proc in the method's context. Procs behave like blocks, but they can be stored in a variable. Lambdas are procs that behave like methods, meaning they enforce arity and return as methods instead of in their parent scope.

What is the difference between a lambda a block and a proc?

A Proc object is an encapsulation of a block of code, which can be stored in a local variable, passed to a method or another Proc, and can be called. Lambdas are anonymous functions, objects of the class Proc, they are useful in most of the situations where you would use a proc.

What keyword does Ruby use to terminate code blocks?

The begin end block can be used to execute a block of code in a specific context. Rescue can be used to handle errors, while ensure will always run at the end of a method. The Ruby documentation has the complete list of Ruby keywords, and it's clearly and thoroughly written. Check it out over here!


1 Answers

Your @action will be a Proc instance and Procs have an arity method so you can check how many arguments the block is supposed to have:

def perform(*args)
  if args.size != @action.arity
    raise WrongArity.new(args.size)
  end
  @action.call(*args)
end

That should take care of splatless blocks like { |a| ... } and { |a,b| ... } but things are a little more complicated with splats. If you have a block like { |*a| ... } then @action.arity will be -1 and { |a,*b| ... } will give you an arity of -2. A block with arity -1 can take any number of arguments (including none), a block with arity -2 needs at least one argument but can take more than that, and so on. A simple modification of splatless test should take care of the splatful blocks:

def perform(*args)
  if @action.arity >= 0 && args.size != @action.arity
    raise WrongArity.new(args.size)
  elsif @action.arity < 0 && args.size < -(@action.arity + 1)
    raise WrongArity.new(args.size)
  end
  @action.call(*args)
end
like image 189
mu is too short Avatar answered Sep 29 '22 21:09

mu is too short