在 Ruby 方法中捕获变量
Posted
技术标签:
【中文标题】在 Ruby 方法中捕获变量【英文标题】:Capturing variables in Ruby methods 【发布时间】:2013-08-04 08:24:59 【问题描述】:在 CoffeeScript 中:
f = ->
v = 5
g = ->
v
g()
f() # returns 5 as expected
在 Ruby 中:
def f
v = 5
def g
v # undefined local variable or method `v' for main:Object (NameError)
end
g
end
f
好的,显然 javascript 函数被设置为在创建它们的范围内捕获变量,但 Ruby 的方法不是。有没有办法让 Ruby 方法在这方面表现得像 JavaScript 函数?
【问题讨论】:
Ruby 没有函数。它有方法,也有块。方法有新的作用域,块有嵌套的作用域。 @JörgWMittag 实际上,正如我从 Fred 的回答中了解到的,Ruby 有方法和 闭包。块只是 Ruby 中的三种闭包之一。还有 Lambdas 和 Procs。 Lambdas 和 Procs 只是通过将块传递给方法来创建的对象。 (或使用 lambda 文字语法。) 【参考方案1】:闭包是具有以下特征的命名代码块(某些语言中的函数,Ruby 中的 lambda 或 proc):
它会记住所有可用变量的值 它被定义的范围(外部范围),即使在另一个范围内调用 或者当这些变量不再可用时(超出范围)。 它是一个对象。因此,它可以分配给变量、传递、在不同范围内调用等。以下是使用 Lambda 的示例。使用 Proc 也可以做到这一点。
minutes = 40
def meditate minutes
return lambda minutes
end
p = meditate minutes
minutes = nil
p.call
# Output:
=> 40
请注意,lambda 是在 meditate 方法中创建的,该方法需要几分钟作为参数。后来,当我们调用 lambda 时,冥想方法已经消失了。我们还将分钟的值设置为零。尽管如此,lambda 还是记住了“分钟”的值。
Ruby 有两种类型的闭包:Lambda 和 Procs。
方法不是闭包,Method 对象也不是。正如 Yukihiro Matsumoto (Matz) 在他的《The Ruby Programming Language》一书中所说:
Method 对象和 Proc 对象之间的一个重要区别是 Method 对象不是闭包。 Ruby 的方法旨在完全自包含,并且它们永远无法访问其范围之外的局部变量。因此,Method 对象保留的唯一绑定是 self 的值 - 要在其上调用方法的对象。
在this post about Methods in the Zen Ruby blog.查看更多信息
【讨论】:
【参考方案2】:要添加到其他答案,为了与您的 CoffeeScript 代码保持最一致,您可能会写:
f = lambda do
v = 5
g = lambda do
v
end
g[]
end
f[]
【讨论】:
【参考方案3】:简短的回答,不。 Ruby 的函数与 Javascript 函数的作用域规则不同。
更长的答案是您可以在 Ruby 中定义对象,以保留定义它们的范围(称为闭包)。这些在 Ruby 中称为 lambdas、Procs 和 blocks。请查看here 快速了解一下。
编辑:块不是 Ruby 对象,但它们可以转换为 Procs,即对象。
【讨论】:
请解释反对意见,以便我了解错误所在。谢谢。 可能反对的是这个“Ruby 的函数”,因为严格来说,Ruby 中没有函数,只有方法和块。【参考方案4】:Ruby 有脚本范围、模块/类/方法定义范围和块范围。只有块创建嵌套范围。因此,您需要使用一个块来定义您的方法。值得庆幸的是,有一种方法可以定义采用块的方法:
def f
v = 5
define_method :g do
v
end
g
end
f
# => 5
但是,请注意,这不会按照您的想法进行(并且您的原始代码也没有)。它确实没有定义了一个嵌套在方法f
中的方法g
。 Ruby 没有嵌套方法。方法总是属于模块(类就是模块),它们不能属于方法。
它的作用是定义一个方法f
,当您运行它时定义一个方法g
,然后调用该方法。
观察:
methods.include?(:g)
# => true
您现在已经定义了一个名为g
的新***方法(实际上是Object
的私有实例方法),并且每次调用f
时都会一遍又一遍地定义它。
你可能想要的是一个 lambda:
def f
v = 5
g = -> v
g.()
end
f
# => 5
在对您写的另一个答案的评论中:
好的,所以我在搞乱 lambda,我注意到我对
g
内部的v
所做的任何事情都不会在v
中反映出来,一旦g
返回。有什么方法可以更改v
stick?
def f
v = 5
g = -> v = 'Hello'
g.()
v
end
f
# => 'Hello'
如您所见,对v
的更改确实在g
返回之后“坚持”。
【讨论】:
所以,g.() 等价于 g.call 和 g.call()!我不知道。 @Fred,还有 g[]。 Proc#[] 文档提到了语法 g.(),并指出 g.() 是 g.call 的语法糖。 Matz 一定很想节省 2 个字符的打字时间!但是谁喜欢到那里去拿那些括号键呢?而且你无论如何都必须按 shift,所以你甚至没有保存任何击键!在我看来,它的语法真的很丑,我不会使用它。 我删除了该评论,因为我意识到我必须致电g
。不过谢谢你的指正。
@7stud 如果我们要发表我们的意见,我想说g.()
显然是对g
的调用,而g.call
看起来更像是对g
的成员。虽然两者在技术上都是后者,但我宁愿把它想象成我在调用 g
,而不是在 g
上调用方法。因此,我将在似乎适用的地方使用.()
。
@anthropomorphic, I'd like to say that g.() is very obviously a call to g, whereas g.call looks more like a call to a member of g
嗯?它们都是g调用的方法,一个方法拼写为'()',另一个拼写为'call'。或者在 ruby 中,一个发送消息“()”给 g,另一个发送消息“call”给 g。 member of g
是什么?【参考方案5】:
在 Ruby 中,您并没有真正在方法内部定义方法,但您可以使用 lambda
:
def f
v = 5
g = lambda do
v
end
g.call
end
【讨论】:
是的。 Ruby 有真正的闭包。 附注你也可以写成g[]
而不是g.call
(如果你想传递任何参数,你可以把它放在括号里)
我真希望 Ruby 有 ()
作为 call
的别名,所以你可以使用 JavaScript 或 Python 样式:g()
。
有谁知道为什么 ruby 使用方括号而不是括号?
我刚刚想到了答案:如果您可以在Proc
上重载()
运算符,那么为了保持一致性,您应该能够在任何其他类上重载()
运算符。由于 Matz 不想要呼叫接线员(无论出于何种原因),我们只剩下 .()
或 []
,这是最接近的替代方案。以上是关于在 Ruby 方法中捕获变量的主要内容,如果未能解决你的问题,请参考以下文章