我可以对传递给方法的块强制执行arity吗?

时间:2012-11-29 07:14:49

标签: ruby lambda proc

有没有办法“开启”使用Proc.newKernel.proc实例化的Proc的严格执行,以便它的行为类似于使用lambda实例化的Proc?

我的initialize方法使用块&action并将其分配给实例变量。我希望action严格执行arity,所以当我稍后向它应用参数时,它会引发ArgumentError我可以拯救并引发更有意义的异常。基本上是:

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

不幸的是,action默认情况下不强制执行arity:

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

action.to_proc不起作用,lambda(&action)也不起作用。

还有其他想法吗?或者更好地解决问题?

谢谢!

3 个答案:

答案 0 :(得分:8)

您的@action将是Proc个实例,而Proc具有arity方法,因此您可以检查该块应该包含多少个参数:

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

那应该照顾像{ |a| ... }{ |a,b| ... }这样的无拼接块,但是对于splats来说情况稍微复杂一些。如果您有{ |*a| ... }之类的阻止,则@action.arity将为-1,而{ |a,*b| ... }将为arity提供-2。具有arity -1的块可以使用任意数量的参数(包括无参数),具有arity -2的块至少需要一个参数但可以使用更多参数,依此类推。一个简单的splatless测试修改应该照顾splatful块:

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

答案 1 :(得分:1)

根据this answer,将proc转换为lambda的唯一方法是使用define_method和朋友。来自docs

  

define_method总是定义一个没有技巧的方法[,即。一个lambda样式的Proc ],即使给出了非lambda Proc对象。这是唯一不会保留技巧的例外。

具体来说,除了实际定义方法之外,define_method(:method_name, &block)还会返回一个lambda。为了在不必要地在一些不良对象上定义一堆方法的情况下使用它,可以在临时对象上使用define_singleton_method

所以你可以这样做:

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

def perform(*args)
  action.call(*args)
  # Could rescue ArgumentError and re-raise a WrongArity, but why bother?
  # The default is "ArgumentError: wrong number of arguments (0 for 1)",
  # doesn't that say it all?
end

private

def to_lambda(&proc)
  Object.new.define_singleton_method(:_, &proc)
end

答案 2 :(得分:-1)

您的解决方案:

class Command
  attr_reader :name, :action

  def initialize(name) # The block argument is removed
    @name   = name
    @action = lambda # We replace `action` with just `lambda`
  end

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

class WrongArity < StandardError; end

一些参考文献: “如果从方法内部调用Proc.new而没有自己的任何参数,它将返回一个新的Proc,其中包含赋予其周围方法的块。” - http://mudge.name/2011/01/26/passing-blocks-in-ruby-without-block.html

事实证明lambda的工作方式相同。