Evaluating (or Not) in Time and Place

One of the least complex (but most-used) macros in Clojure is comment, which entirely avoids evaluating the code contained in it:

context/comment.clj
 
(​defmacro​ ​comment
 
"Ignores body, yields nil"
 
{:added ​"1.0"​}
 
[& body])
 
 
(​comment
 
(​println​ ​"wow"​)
 
(​println​ ​"this macro is incredible"​))
 
;=> nil
 
 
(​+​ 1 2) ​; this is another type of comment
 
(​+​ 1 2) #_(​println​ ​"this is yet another"​)

There are a couple of other commenting mechanisms in Clojure, but comment is the only one that’s a macro (the others are built into the Clojure reader). The return value of comment is always nil, and none of the code passed to it is ever evaluated. Since it’s a macro, the code passed in has to be syntactically correct so that it is readable. One upside of using the comment macro is that you get syntax highlighting in your editor of choice. So it’s fairly common in practice to use comment to show examples of how to use code in a particular namespace. I don’t personally use it much, as I prefer to use unit tests that execute and don’t go out of date instead. But it’s nice to have it available when I need it. So comment gives us a degenerate case of evaluating code in some other context, in the same way that /dev/null gives us a degenerate place to send a stream of output.

A more interesting example of using macros to evaluate code in a different context is dosync, the entry point to Clojure’s software transactional memory (STM) system:

context/dosync.clj
 
(​defmacro​ ​dosync
 
"Runs the exprs (in an implicit do) in a transaction that encompasses
 
exprs and any nested calls. Starts a transaction if none is already
 
running on this thread. Any uncaught exception will abort the
 
transaction and flow out of dosync. The exprs may be run more than
 
once, but any effects on Refs will be atomic."
 
{:added ​"1.0"​}
 
[& exprs]
 
`(​sync​ nil ~@exprs))
 
 
(​defmacro​ ​sync
 
"transaction-flags => TBD, pass nil for now
 
 
Runs the exprs (in an implicit do) in a transaction that encompasses
 
exprs and any nested calls. Starts a transaction if none is already
 
running on this thread. Any uncaught exception will abort the
 
transaction and flow out of sync. The exprs may be run more than
 
once, but any effects on Refs will be atomic."
 
{:added ​"1.0"​}
 
[flags-ignored-for-now & body]
 
`(​.​ clojure.lang.LockingTransaction
 
(runInTransaction (​fn​ [] ~@body))))
 
 
(​def​ ant-1 (​ref​ {:id 1 :x 0 :y 0}))
 
(​def​ ant-2 (​ref​ {:id 2 :x 10 :y 10}))
 
 
(​dosync
 
(​alter​ ant-1 ​update-in​ [:x] ​inc​)
 
(​alter​ ant-1 ​update-in​ [:y] ​inc​)
 
(​alter​ ant-2 ​update-in​ [:x] ​dec​)
 
(​alter​ ant-2 ​update-in​ [:y] ​dec​))

Any Clojure STM example you’ve ever looked at allows you to send code to be evaluated in a transaction, to be retried in case of conflicts, and the dosync macro (along with the underlying sync) bundles the argument expressions into a suitable form (a thunk). I find most folks don’t need to use the STM system that often, so dosync is relatively rare outside of Clojure language introductions, but it’s really handy to have it generate code for you. Otherwise you’d need to type out (or set up editor automation for) the LockingTransaction, anonymous function creation, and the rest.

Because we’ve got the power to say when and in what context we want to evaluate a bit of code, we can even do things like sending code to be evaluated in a future[14] (an instance of java.util.concurrent.Future, specifically). The future macro from clojure.core wraps its argument expressions up into a function and submits that function to an Executor (which works since Clojure functions implement java.util.concurrent.Callable):

context/future.clj
 
(​defmacro​ ​future
 
"Takes a body of expressions and yields a future object that will
 
invoke the body in another thread, and will cache the result and
 
return it on all subsequent calls to deref/@. If the computation has
 
not yet finished, calls to deref/@ will block, unless the variant of
 
deref with timeout is used. See also - realized?."
 
{:added ​"1.1"​}
 
[& body] `(​future-call​ (^{:once true} fn* [] ~@body)))
 
 
(​def​ f (​future​ (Thread/sleep 5000)
 
(​println​ ​"done!"​)
 
(​+​ 41 1)))
 
 
@f
 
;=> 42 (after sleeping 5 seconds and then printing "done!")

There are a couple of interesting things going on here. The first is that the macro uses an underlying function, future-call, to do most of the work. In fact, future itself is just a thin wrapper around that function. Just like our eventual with-out-file implementation, this gives us the best of both worlds. We can use the underlying future-call function whenever we need it, but we still have the succinctness of the macro version for normal use. The second interesting thing is the {:once true} metadata on the fn*. This is a fairly low-level compiler feature to help us avoid accidental memory leaks. It makes sure that any closed-over locals in the function get cleared after the function is called. This way, Clojure doesn’t have to hold onto those values indefinitely, wrongly thinking that you might call the function again. Anytime we create functions that we know will only be invoked once (or if we at least know we don’t need closed-over locals in the function invocation), it’s a good idea to use (^:once fn* [] ...) instead of the plain (fn [] ...), to avoid leaking memory. ^:keyword-here, by the way, is just a shorthand for the common ^{:keyword-here true} pattern for Clojure metadata.

context/once.clj
 
(​let​ [x :a
 
f (^:once fn* [] (​println​ x))]
 
(f) ​;; prints :a
 
(f)) ​;; prints nil
 
 
(​let​ [x :a
 
f (​fn​ [] (​println​ x))]
 
(f) ​;; prints :a
 
(f)) ​;; prints :a

In the ^:once-decorated version, after the first evaluation of f, the local x gets cleared, so the second evaluation has x bound to nil. Clearly this is useful only when the function will execute just once. Note that it’s necessary to use fn* here, not fn, to get the benefit of this optimization. Of course, if the function will execute multiple times, or if you don’t mind leaking the memory that your locals consume, you can always use the usual fn for your function definitions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.85.221