简体   繁体   中英

When to use Clojure's closures?

I've written couple of Clojure programs, but I can barely remember that I used closures.

What are the best use cases for using closures in Clojure?

Also, can you provide your use cases and examples that might helpful for beginners as well.

This is an overly simple example, but I use closures to "partially apply" functions when I already have 1 argument, but need to get another argument later. You can solve that by doing something like this inside a function:

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

I use them all the time, but their use is so simple it's hard to think of an example that isn't contrived.

I found this in a recent project I did with Lorenz Systems. Hue-f is a function that when given the x, y and z coordinates of a Lorenz System, returns a certain hue to color that section of the line. It forms a Closure over the random generator:

(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 

Although in this case the closure is unnecessary since rand-gen is global anyways. If I wanted to "make it necessary", I could change it to something like:

(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 

(I think I got the brackets right there. Lisps are hard to write "free hand".

A closure allows you to capture some state for later use, even after the context in which the state was created is out of your reach. It's almost like a "portal" of sorts: you reach your hand through the portal and you can grab on to something which is not in your current realm.

So, the most useful case for closures is when you need to control or access something, but don't have the original handle to it.

One use case is the following: let's say I want to create a thread that goes off on its own to do some work, but I'd like to be able to "talk to" that thread and say, "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))

There are several ways to accomplish this (explicitly creating a thread and interrupting it, for example), but this method returns a function which, when evaluated, modifies the value of the closed-over variable keep-going so that the thread running in a completely different context sees the change and stops looping.

It's as if I reach my hand through the portal and flip the switch; without the closure, I'd have no way to grab hold of keep-going .

Closures, currying and partial application play a significant part in the composability of a language. Which, by design, provides a degree of flexibility in constructing functions adapting to the expected outcome that are not statically defined.

The term closure comes from the ability to "close" over a variable in one scope and carry the value of the closed over variable beyond the scope of where it was declared.

Closures can be passed around like any other data/function reference.

And, unlike functions, closures are not evaluated at time of declaration. I forget the lambda calculus term for this, but it is a way to defer production of the result.

You declare a closure like a anonymous function (using either the (fn []...) or #(...) shorthand the one difference that differentiates it from lambda is if it closes over a variable passed in through a higher level scope.

A good example of using a closure is when you return a function from a function. That inner function still "remembers" the variables it was created nearby.

Example:

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

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

A better (and real) example might be a middleware pattern that is widely used in HTTP processing. Let's say you want to assign a user to each HTTP request. Here is a middleware for that:

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

A handler parameter is a HTTP handler. Now, every request will have a :user field filled with either a map of user data or nil value.

Now you wrap your handler with the middleware (Compojure syntax):

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

Since your inner function references the handler variable, you are using a closure.

You have never written a function for map or a predicate for filter that drew on the environment?

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

... or, giving the predicate a name:

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

This closes over the constant 21 . It might otherwise have been drawn from country or realm of activity (marriage, criminal responsibility, voting rights) or whatever.

In addition to other answers, caching is often a good place to use closures. If you can afford in-memory caching, one of the simplest way is to put the cache in the closure :

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

Depending on the context of execution you may use an atom instead of a volatile. Memoizing is also made with a closure and is a specialized caching technique. It's just that you don't need to implement it yourself​, Closure provides memoize .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM