何时使用Clojure的闭包?

时间:2017-05-24 19:19:25

标签: clojure functional-programming closures lisp clojurescript

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

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

另外,您是否可以提供可能对初学者有用的用例和示例。

6 个答案:

答案 0 :(得分:3)

这是一个非常简单的例子,但是当我已经有一个参数时,我使用闭包来“部分应用”函数,但是后来需要再获得另一个参数。你可以通过在函数中执行类似的操作来解决这个问题:

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

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

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

(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是全局的。如果我想“让它变得必要”,我可以将其改为:

(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很难写出“自由的手”。

答案 1 :(得分:2)

闭包允许您捕获某个状态供以后使用,即使在创建状态的上下文不可及之后也是如此。它几乎就像是各种各样的“门户”:你通过门户到达你的手,你可以抓住一些不在你当前领域的东西。

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

一个用例如下:假设我想创建一个独立的线程来完成一些工作,但我希望能够“跟”那个线程说“你的工作”完了,请停止你正在做的事情。“

(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))

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

就好像我从门口伸出手并翻开开关;没有关闭,我无法抓住keep-going

答案 2 :(得分:2)

闭包,currying和部分应用在语言的可组合性中起着重要作用。根据设计,它在构建适应未静态定义的预期结果的函数时提供了一定程度的灵活性。

术语闭包来自于“关闭”一个范围内的变量并将闭合变量的值带到超出声明范围的范围。

闭包可以像任何其他数据/函数引用一样传递。

并且,与函数不同,闭包在声明时不会被评估。我为此忘记了lambda演算术语,但它是推迟生成结果的一种方法。

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

答案 3 :(得分:2)

使用闭包的一个很好的例子是从函数返回函数。那个内在的功能仍然记得"它附近创建的变量。

示例:

(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变量,所以你正在使用一个闭包。

答案 4 :(得分:0)

你从来没有为map写一个函数,或者为filter写了一个吸引环境的谓词?

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

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

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

这会关闭常量21。否则它可能来自国家或活动领域(婚姻,刑事责任,投票权)等等。

答案 5 :(得分:0)

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

(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