如何在Proc定义时使用变量的值,而不是通过引用变量来定义Proc?或者我如何处理根据输入序列定义要按顺序执行的不同步骤列表的问题?
例:
arr = []
results = [1,2,3]
for res in results
arr << Proc.new { |_| res }
end
p arr[0].call(42)
p arr[1].call(3.14)
预期产出:
1
2
实际输出:
3
3
为什么你的代码不能按预期工作:共享闭包范围
根据定义,Proc 是一个闭包,它保留其原始作用域,但延迟执行直到被调用。您的非惯用代码掩盖了几个细微的错误,包括 for-in 控件表达式不会创建为闭包提供正确上下文的作用域门。所有三个 Proc 对象共享相同的作用域,其中对res变量的最终赋值是3
。由于它们的共享作用域,在调用数组中存储的任何 Proc 时,您可以正确获得相同的返回值。
修复您的闭合
您可以通过一些小的更改来使您的代码工作。例如:
arr = []
results = [1,2,3]
results.map do |res|
arr << Proc.new { |_| res }
end
p arr[0].call(42) #=> 1
p arr[1].call(3.14) #=> 2
潜在的重构
一种更惯用的方法
除了创建适当的作用域门之外,更惯用的重构可能如下所示:
results = [1, 2, 3]
arr = []
results.map { |i| arr << proc { i } }
arr.map { |proc_obj| proc_obj.call }
#=> [1, 2, 3]
其他改进
进一步的重构可以进一步简化示例代码,特别是如果您不需要将输入存储在中间变量或解释变量(如结果(中。考虑:
array = [1, 2, 3].map { |i| proc { i } }
array.map &:call
#=> [1, 2, 3]
验证重构
因为 Proc 不关心 arity,所以当 Proc#call 传递任意参数时,这种通用方法也有效:
[42, 3.14, "a", nil].map { |v| arr[0].call(v) }
#=> [1, 1, 1, 1]
[42, 3.14, "a", nil].map { |v| arr[1].call(v) }
#=> [2, 2, 2, 2]
问题是proc
对象使用循环内的上下文,以下内容应该有效
def proc_from_collection(collection)
procs = []
collection.each { |item| procs << Proc.new { |_| item } }
procs
end
results = [1,2,3]
arr = proc_from_collection(results)
p arr[0].call # -> 1
p arr[1].call # -> 2
读完托德·A·雅各布斯的回答后,我觉得我错过了什么。
在stackoverflow上阅读一些关于Ruby中for loop
的文章让我意识到我们这里不需要一种方法。
我们可以使用一种不会像for loop
那样用不必要的变量污染全局环境的方法迭代数组。
我建议在您需要根据词法范围行为的适当闭包时使用一种方法(函数的主体在定义函数的环境中进行评估,而不是在环境中评估 调用函数的位置。
我的第一个答案仍然是一个很好的第一种方法,但正如 Todd A. Jacobs 所指出的那样,在这种情况下,迭代数组的"更好"方法就足够了
。arr = []
results = [1,2,3]
results.each { |item| arr << Proc.new { |_| item } }
p arr[0].call # -> 1
p arr[1].call # -> 2