【问题标题】:Capturing variables in Ruby methods在 Ruby 方法中捕获变量
【发布时间】: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 文字语法。)

标签: javascript ruby coffeescript closures


【解决方案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 不想要呼叫接线员(无论出于何种原因),我们只剩下 .()[],这是最接近的替代方案。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-09-08
      • 2020-03-21
      • 2012-11-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多