如何在Ruby的Proc/Lambda中使用真正的本地变量

12

初学者 Ruby 问题。 最简单的方法是如何更改此代码,但完全保留块,消除副作用?

x = lambda { |v| x = 2 ; v}
x.call(3)
#=> 3
x
#=> 2

这是我能想到的最简单的例子,用来说明我的问题,因此“删除分配”或“不将Proc分配给x”不是我要找的。

我希望在一个Proc(或lambda)中设置本地变量,而不会影响原始封闭范围。我可以动态创建一个类或模块来包装代码块,但对于这样一个基本操作来说,那似乎有点过度了。

相当于我所尝试做的Python代码:

def x(v):
  x = 2  # this is a local variable, what a concept
  return v

你尝试过不使用外部变量的相同名称吗? - Maurício Linhares
如果在外部作用域中声明,x 就是一个闭包变量。你是否有任何条件禁止重命名其中的变量,例如 _x?否则,您将试图禁用 Ruby 语言中的基本期望。 - tadman
很明显,在方法中可以有局部变量。那么,是不是只有这种语言结构会在VM中创建新的词法作用域呢?如果是这样的话,似乎Proclambda都不能用于进行适当的函数式编程,因此它们是廉价的。 - wberry
2个回答

28

有时这是期望的行为:

total = 0
(1..10).each{|x| total += x}
puts total

但有时候变量的命名是偶然的,你不想改变一个恰好与之同名的外部变量。在这种情况下,可以在参数列表后面加上分号,并列出代码块内的本地变量:

x = lambda{|v; x| x = 2; v}
p x.call(3) #3
p x #<Proc:0x83f7570@test1.rb:2 (lambda)>

2
在参数列表中定义一个本地变量是个好主意!这样做可以完全实现 OP 想要的功能,而不必从绑定中释放它。 - Chris Heald
4
哇塞,我每天都会学到新东西——我不知道你可以在参数列表中使用分号这样“声明”本地变量 o.O - nzifnab
2
@Dog 你可能正在运行 Ruby 1.8.7 - 它确实可以在 1.9.3 上运行(http://ideone.com/cPeDpd)。 - steenslag
已经在 Ruby 1.9.3 上验证过。感谢这个教训! - wberry

6
这是因为Lambda绑定到其定义作用域(而不是调用作用域),并且是完整的闭包,其中包括局部变量。你真正想要的是一个未绑定的proc,可以传递并在没有特定绑定的情况下调用。这并不是Ruby很容易做到的事情(使用eval可能更像一个字符串语句而不是块)。Proc、Lambda和Block都绑定到它们的定义作用域上。词法作用域仅在类、模块和方法中建立;不在block/proc/lambda/其他任何地方建立。
值得注意的是,Python甚至根本不允许在Lambda中进行赋值。

eval的可能性很有吸引力。但是那只会将变量绑定到执行eval的作用域中吗?我猜这基本上会导致性能损失。 - wberry
是的,不幸的是,Python 的 lambda 受到限制,因为它实现了单个表达式,这就阻止了在其中进行赋值(以及while循环和其他语句)操作。这是一个语法上的原因。 - wberry
正确。即使该范围为空,Ruby 中执行的所有内容都绑定到某个包含范围。 - Chris Heald

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接