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

Functional programming 关于闭包中词汇绑定的更多解释?,functional-programming,lisp,clojure,closures,Functional Programming,Lisp,Clojure,Closures,与此相关的帖子太多了,但我再次问这个问题的目的不同 我试图理解为什么闭包是重要和有用的。我在其他与此相关的SO帖子中读到的一件事是,当你将一个变量传递给闭包时,闭包从那时起就开始记住这个值。这是它的整个技术方面,还是发生了更多的事情 我想知道的是,当闭包内部使用的变量从外部修改时会发生什么。它们应该是常量吗 在Clojure语言中,我可以做到以下几点:但由于存在不可变的值,因此不会出现此问题。那么其他语言呢?闭包的正确技术定义是什么 (defn make-greeter [greeting-pr

与此相关的帖子太多了,但我再次问这个问题的目的不同

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

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

在Clojure语言中,我可以做到以下几点:但由于存在不可变的值,因此不会出现此问题。那么其他语言呢?闭包的正确技术定义是什么

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

((make-greeter "Hello") "World")

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


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

闭包实际上是一种数据结构,编译器使用它来确保函数始终能够访问它需要操作的数据。下面是一个函数的示例,该函数在定义时进行记录

(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"
定义此函数时,将创建一个类,并在存储foo值的堆上分配一个小结构(闭包)。该类有一个指向该类的指针(或者它包含该指针,我不确定)。如果您再次运行此操作,那么将分配第二个闭包来保存另一个foo。当我们说“这个函数结束于foo”时,我们的意思是说它引用了一个structure/class/任何在编译时存储foo状态的东西。您需要关闭某个对象的原因是,在使用数据之前,包含该对象的函数将消失。在这种情况下,outer(包含foo的值)将在使用foo之前结束并消失很久,因此没有人会修改foo。当然,foo可以给某人一个参考,然后修改它

词法闭包是指通过引用将包含的变量(例如示例中的
问候语前缀
)包含在内的闭包。创建的闭包在创建时不只是获取
问候语前缀的值,而是获取一个引用。如果在创建闭包后修改了
问候语前缀
,则每次调用闭包时都会使用它的新值

在纯函数式语言中,这没有多大区别,因为值永远不会更改。因此,如果将
问候语前缀
的值复制到闭包中并不重要:引用原始前缀和其副本时,行为上没有可能的差异

在“带闭包的命令式语言”中,例如C#和Java(通过匿名类),必须决定封闭变量是由值还是由引用封闭。在Java中,这个决定是通过只允许
final
变量被封装而预先做出的,就变量而言,它有效地模仿了函数式语言。在C#中,我认为这是另一回事


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

你可能会喜欢阅读,它描述了如何在C和F中进行比较。

看看这篇博文:。它展示了闭包在锁定数据问题上的一个很好的应用,这样数据就可以通过特定的接口进行独占访问(使数据类型不透明)

这种类型锁定背后的主要思想更简单地用反例说明,在我撰写此答案时,怀远用CommonLisp发布了反例。事实上,Clojure版本很有趣,因为它表明,如果Clojure中的变量恰好包含一个引用类型的实例,则闭式变量更改其值的问题确实会出现

(defn create-counter []
  (let [counter (atom 0)
        inc-counter! #(swap! counter inc)
        get-counter (fn [] @counter)]
    [inc-counter! get-counter]))
对于最初的makegreeter示例,您可以这样重写它(注意deref/@):

然后,您可以使用它向网站各个部分的不同运营商发送个性化问候语。:-)


有关更多说明,请参见示例:


这并不是那种似乎在这里赢得选票的答案,但我衷心地敦促你通过阅读克里希纳穆提(免费!)(在线!)的教科书来发现你问题的答案

我将非常非常简要地解释这本书,总结它引导你通过的微小口译员的发展:

  • 算术表达式语言(AE)
  • 具有命名表达式的算术表达式语言(WAE); 实现这一点需要开发一个可以 用值替换名称
  • 一种添加一阶函数的语言(F1WAE):使用一个函数需要替换 每个属性的值
    (defn make-greeter [greeting-prefix]
        (fn [username] (str @greeting-prefix ", " username)))
    
    ((make-greeter "Hello from Gizmos Dept") "John")
    ((make-greeter "Hello from Gadgets Dept") "Jack").
    
    CL-USER> (let ((x (list 1 2 3)))
               (prog1
                   (let ((y x))
                     (lambda () y))
                 (rplaca x 2)))
    #<COMPILED-LEXICAL-CLOSURE #x9FEC77E>
    CL-USER> (funcall *)
    (2 2 3)