有没有办法“打开”使用Proc.new
或Kernel.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)
也不起作用。
还有其他想法吗?或者更好地解决问题?
谢谢!
你的@action
将是一个Proc
实例,Proc
s有一个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| ... }
会给你一个-2的arity
。具有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
根据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
你的解决方案
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以相同的方式工作。