关于闭包词汇绑定的更多解释?

use*_*855 8 lisp closures functional-programming clojure

有很多与此相关的SO帖子,但我再次提出这个问题的目的不同

我试图理解为什么闭包很重要和有用.我在与其相关的其他SO帖子中读过的一件事是,当你将一个变量传递给闭包时,闭包开始从那时开始记住这个值.这是它的整个技术方面还是有更多的事情发生在那里.

我想知道当封闭内部使用的变量从外部修改时会发生什么.它们应该只是常量吗?

在Clojure语言中,我可以执行以下操作:但由于值是不可变的,因此不会出现此问题.那么其他语言怎么样?闭包的正确技术定义是什么?

(defn make-greeter [greeting-prefix]
    (fn [username] (str greeting-prefix ", " username)))

((make-greeter "Hello") "World")
Run Code Online (Sandbox Code Playgroud)

Joh*_*nts 9

这不是那种似乎在这里起床的答案,但我衷心地敦促你通过阅读Shriram Krishnamurthi(免费!)(在线!)教科书,编程语言:应用和解释来发现你的问题的答案.

我将通过总结其引导您完成的极小解释器的发展,非常简短地解释这本书:

  • 算术表达语言(AE)
  • 具有命名表达式(WAE)的算术表达式语言; 实现这一点涉及开发一个可以用值替换名称的替换函数
  • 添加一阶函数的语言(F1WAE):使用函数涉及为每个参数名称替换值.
  • 相同的语言,没有替代:事实证明,"环境"允许您避免先发制人替换的开销.
  • 通过允许在任意位置定义函数来消除函数和表达式之间分离的语言(FWAE)

这是关键点:你实现了这个,然后你发现使用替换它可以正常工作,但是在环境中它被破坏了.特别是,为了解决这个问题,您必须确保将评估的函数定义与评估时所使用的环境相关联.这对(fundef +定义环境)就是所谓的"闭包".

呼!

好的,当我们为图片添加可变绑定时会发生什么?如果您自己尝试这样做,您将看到自然实现取代了将名称与值相关联的环境以及将名称与绑定相关联的环境.这与封闭的概念是正交的; 由于闭包捕获环境,并且由于环境现在将名称映射到绑定,因此您将获得所描述的行为,从而在环境中捕获的变量的突变是可见且持久的.

再次,我非常希望你看一下PLAI.

  • 神奇的书.+1! (2认同)

Art*_*ldt 5

闭包实际上是编译器使用的数据结构,用于确保函数始终可以访问所需的数据.这是一个在定义时记录的函数示例.

(defn outer []
    (let [foo (get-time-of-day)]
      (defn inner []
          #(str "then:" foo " now:" (get-time-of-day)))))


(def then-and-now (outer))
(then-and-now)    ==> "then:1:02:03 now:2:30:01"
....
(then-and-now)    ==> "then:1:02:03 now:2:31:02"
Run Code Online (Sandbox Code Playgroud)

定义此函数时,将创建一个类,并在堆上分配一个小结构(闭包),用于存储foo的值.该类有一个指向它的指针(或者它包含它我不确定).如果你再次运行它,那么将分配第二个闭包以保持其他foo.当我们说"这个函数关闭foo"时,我们的意思是它引用了一个stricture/class /在编译时存储foo状态的任何东西.您需要关闭某些内容的原因是因为包含它的函数在使用数据之前就会消失.在这种情况下,外部(包含foo的值)将结束并在foo使用之前消失很长时间,因此没有人会修改foo.当然,foo可能会给那些可以修改它的人提供参考.


Edm*_*und 5

词法闭合是其中所述封闭变量(例如greeting-prefix在你的例子)由参考包围.创建的闭包不是简单地获取创建时的值greeting-prefix,而是获取引用.如果greeting-prefix在创建闭包后修改了,则每次调用闭包时它的新值都将被闭包使用.

在纯函数式语言中,这并不是一个区别,因为值永远不会改变.因此,如果将值greeting-prefix复制到闭包中并不重要:引用原始版本与其副本可能会产生不同的行为差异.

在"命令式语言与闭包"中,例如C#和Java(通过匿名类),必须做出关于封闭变量是由值还是通过引用括起来的一些决定.在Java中,这个决定只是允许包含final变量,有效地模仿函数语言,就该变量而言.在C#中,我认为这是另一回事.

按值封闭简化了实现:要封装的变量通常存在于堆栈中,因此当构造闭包的函数返回时将被销毁 - 这意味着它不能被引用括起来.如果需要通过引用进行封装,则解决方法是识别此类变量,并在每次调用该函数时将它们保存在分配的对象中.然后,此对象将作为闭包环境的一部分保留,并且只要所有使用它的闭包都是活动的,就必须保持活动状态.(我不知道是否有任何编译语言直接使用这种技术.)


Jon*_*erg 2

您可以将闭包视为一个“环境”,其中名称绑定到。这些名称对于闭包来说完全是私有的,这就是为什么我们说它“关闭”了它的环境。所以你的问题没有意义,因为“外部”无法影响封闭的环境。是的,闭包可以引用全局环境中的名称(换句话说,如果它使用未绑定在其私有封闭环境中的名称),但这是一个不同的故事。

如果您愿意,您可以将环境视为字典或哈希表。闭包有自己的小字典,可以在其中查找名称。