什么时候使用 Clojure 的闭包?

When to use Clojure's closures?

我写过几个 Clojure 程序,但我几乎不记得我使用了闭包。

在 Clojure 中使用闭包的最佳用例是什么?

此外,您能否提供对初学者也有帮助的用例和示例。

这是一个过于简单的示例,但是当我已经有 1 个参数但稍后需要获取另一个参数时,我对 "partially apply" 函数使用闭包。你可以通过在函数中做这样的事情来解决这个问题:

(let [x 10
      f #(- % x)]
   f)

我一直在使用它们,但它们的使用非常简单,很难想出一个不做作的例子。

我在最近与 Lorenz Systems 合作的项目中发现了这一点。 Hue-f 是一个函数,当给定 Lorenz 系统的 x、y 和 z 坐标时,returns 某种色调会为线条的该部分着色。它在随机生成器上形成一个闭包:

(def rand-gen (g/new-rand-gen 99))
(def hue-f #(co/test1 % %2 %3 (g/random-double 1 1.05 rand-gen) 1.7))
                                                      ; ^ Closure 

尽管在这种情况下闭包是不必要的,因为 rand-gen 无论如何都是全局的。如果我想 "make it necessary",我可以将其更改为:

(def hue-f
  (let [rand-gen (g/new-rand-gen 99)]
    #(co/test1 % %2 %3 (g/random-double 1 1.05 rand-gen) 1.7)))
                                             ; ^ Closure 

(我想我的括号就在那里。Lisps 很难写 "free hand"。

闭包允许您捕获一些状态供以后使用,即使创建状态的上下文超出您的范围。这几乎就像某种 "portal":你通过传送门伸出手,你可以抓住不在你当前领域的东西。

因此,闭包最有用的情况是当您需要控制或访问某些东西,但没有它的原始句柄时。

一个用例如下:假设我想创建一个线程,该线程自行关闭以完成一些工作,但我希望能够 "talk to" 该线程并说, "your work is done, please stop what you're doing."

(defn do-something-forever
  []
  (let [keep-going (atom true)
        stop-fn #(reset! keep-going false)]
    (future (while @keep-going
              (println "Doing something!")
              (Thread/sleep 500))
            (println "Stopping..."))
    stop-fn))

有几种方法可以实现这一点(例如,明确地创建一个线程并中断它),但是这个方法 returns 一个函数,在计算时修改封闭变量的值 keep-going 以便在完全不同的上下文中的线程 运行 看到更改并停止循环。

就好像我把手伸进了传送门并按下了开关;没有关闭,我将无法抓住 keep-going.

闭包、柯里化和部分应用在语言的可组合性中起着重要作用。通过设计,它在构造适应非静态定义的预期结果的函数方面提供了一定程度的灵活性。

术语闭包来自于"close" 在一个范围内对变量进行处理,并将封闭变量的值携带到声明范围之外的能力。

闭包可以像任何其他 data/function 引用一样传递。

而且,与函数不同的是,闭包在声明时不会求值。我忘记了这个的 lambda 演算术语,但它是一种推迟产生结果的方法。

你像匿名函数一样声明一个闭包(使用 (fn []...)#(...) shorthand 它与 lambda 的一个区别是它是否关闭传入的变量通过更高级别的范围。

使用闭包的一个很好的例子是当您 return 从一个函数中创建一个函数时。该内部函数仍然 "remembers" 它在附近创建的变量。

示例:

(defn get-caller [param1 param2]
  (fn [param3]
    (call-remote-service param1 param2 param3)))

(def caller (get-caller :foo 42))
(def result (caller "hello"))

一个更好(和真实)的例子可能是在 HTTP 处理中广泛使用的中间件模式。假设您要为每个 HTTP 请求分配一个用户。这是一个中间件:

(defn auth-middleware [handler]
  (fn [request] ;; inner function
    (let [user-id (some-> request :session :user_id)
          user (get-user-by-id user-id)]
      (handler (assoc request :user user)))))

一个handler参数是一个HTTP处理程序。现在,每个请求都会有一个 :user 字段,其中填充了用户数据映射或 nil 值。

现在你用中间件包装你的处理程序(Compojure 语法):

(GET "/api" request ((auth-middleware your-api-handler) request))

由于您的内部函数引用了 handler 变量,您使用的是闭包。

您从未为 map 编写过利用环境的函数或 filter 的谓词吗?

(let [adults (filter #(>= (:age %) 21) people)] ... )

... 或者,为谓词命名:

(def adult? #(>= (:age %) 21))

这将关闭常量 21。否则它可能来自 activity 的国家或领域(婚姻、刑事责任、投票权)或其他任何东西。

除了其他答案,缓存通常是使用闭包的好地方。如果你能负担得起内存缓存,最简单的方法之一是将缓存放在闭包中:

(defn cached-get-maker []
  (let [cache (volatile nil)]
    (fn [params]
      (if @cache
        @cache
        (let [result (get-data params)]
          (vreset! cache result)
          result)))))

根据执行上下文,您可以使用 atom 而不是 volatile。 Memoizing 也是用闭包制作的,是一种专门的缓存技术。只是不需要自己实现 ,Closure提供memoize.