6

有没有办法“打开”使用Proc.newor实例化的 Proc 的严格数量强制Kernel.proc,以便它的行为类似于用 实例化的 Proc lambda

我的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)

还有其他想法吗?还是解决问题的更好方法?

谢谢!

4

3 回答 3

8

@action将是一个Proc实例,并且Procs 有一个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
于 2012-11-29T07:34:01.847 回答
1

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

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
于 2012-11-29T14:08:39.677 回答
-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 以相同的方式工作。

于 2012-11-29T08:29:57.050 回答