我可以对传递给方法的块强制执行 arity 吗?
Posted
技术标签:
【中文标题】我可以对传递给方法的块强制执行 arity 吗?【英文标题】:Can I enforce arity on a block passed to a method? 【发布时间】:2012-11-17 05:18:18 【问题描述】:有什么方法可以“开启”使用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
。只需删除操作参数并在设置实例变量的行上,将action
替换为lambda
。我在下面发布了一个代码示例。
【参考方案1】:
您的@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| ...
这样的无溅块,但是溅出的东西会稍微复杂一些。如果你有一个像 |*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
【讨论】:
你是对的,这解决了问题。但我会等待,看看是否有更简洁的方法来做到这一点。我正在寻找的行为与 lambda Procs 相同,因此如果不需要,我不想重新实现它(当然,我的 Command 类已经在重新实现 Proc,但不可避免地会有更多的域-具体行为) @rickyrickyrice:是的,它看起来确实有点笨拙,但我想不出更好的了。 OTOH,这里已经很晚了,所以我可能会遗漏一些明显的东西。 除了使用 lambda 之外别无他法。这就是 ruby 的设计方式:proc 没有数量检查。从 1.9.2 开始,ruby 强制执行过程验证。 我认为它应该严格小于第二个条件,因为可以将 0 args 传递给一个 proc of arity -1。另请注意,您可以结合条件来消除elsif
:if (action.arity >= 0 && args.size != action.arity) || args.size < -(action.arity + 1)
@AndyH:你在<
与<=
上是对的,但我认为没有必要合并分支,更短 并不意味着 更清晰。【参考方案2】:
根据this answer,将proc 转换为lambda 的唯一方法是使用define_method
和朋友。来自docs:
define_method
总是定义一个没有技巧的方法 [i.e.一个 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
【讨论】:
【参考方案3】:你的解决方案:
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 以相同的方式工作。
【讨论】:
很有趣,但我认为它不起作用:(irb):7: 警告:试图创建没有块的 Proc 对象 实际上我认为它确实有效,尽管有警告,但它确实发出这些警告的事实表明它不是一个理想的解决方案! 所以当我复制/粘贴代码示例(并在您的 OP 中运行检查)时,我没有收到任何警告。但是当我单独运行它时,我会这样做。同样,它似乎只是将块转换为 lambda,如果它传递了一个 proc,它似乎不会转换它。我认为上述两种解决方案几乎肯定更犹太洁食,但如果你只是将块转换为 lambdas 并且总是保证一个块,我认为这没有任何问题。以上是关于我可以对传递给方法的块强制执行 arity 吗?的主要内容,如果未能解决你的问题,请参考以下文章
我可以在 Gradle 中设置一个变量并在编译时将其传递给 Java 吗?
我可以强制 R data.table %like% 使用“fixed = TRUE”吗?