在 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 返回。有什么方法可以更改vstick?

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 方法中捕获变量的主要内容,如果未能解决你的问题,请参考以下文章

Ruby知识概要

在类方法中使用实例变量 - Ruby

为啥你可以在函数中使用另一种方法而不是赋值运算符来更改 Ruby 中局部变量的值?

Ruby TracePoint:如何捕获特定类的定义?

Ruby中方法的静态局部变量?

Ruby:自动将实例变量设置为方法参数?