- •Contents
- •Foreword
- •Acknowledgments
- •Preface
- •Who This Book Is For
- •What Is in This Book
- •How to Read This Book
- •Notation Conventions
- •Web Resources and Feedback
- •Downloading Sample Code
- •Getting Started
- •Why Clojure?
- •Clojure Coding Quick Start
- •Exploring Clojure Libraries
- •Introducing Lancet
- •Wrapping Up
- •Exploring Clojure
- •Forms
- •Reader Macros
- •Functions
- •Vars, Bindings, and Namespaces
- •Flow Control
- •Metadata
- •Wrapping Up
- •Working with Java
- •Calling Java
- •Optimizing for Performance
- •Creating and Compiling Java Classes in Clojure
- •Exception Handling
- •Adding Ant Projects and Tasks to Lancet
- •Wrapping Up
- •Unifying Data with Sequences
- •Everything Is a Sequence
- •Using the Sequence Library
- •Clojure Makes Java Seq-able
- •Adding Properties to Lancet Tasks
- •Wrapping Up
- •Functional Programming
- •Functional Programming Concepts
- •How to Be Lazy
- •Lazier Than Lazy
- •Recursion Revisited
- •Wrapping Up
- •Concurrency
- •The Problem with Locks
- •Refs and Software Transactional Memory
- •Use Atoms for Uncoordinated, Synchronous Updates
- •Use Agents for Asynchronous Updates
- •Managing Per-Thread State with Vars
- •A Clojure Snake
- •Making Lancet Targets Run Only Once
- •Wrapping Up
- •Macros
- •When to Use Macros
- •Writing a Control Flow Macro
- •Making Macros Simpler
- •Taxonomy of Macros
- •Making a Lancet DSL
- •Wrapping Up
- •Multimethods
- •Living Without Multimethods
- •Moving Beyond Simple Dispatch
- •Creating Ad Hoc Taxonomies
- •When Should I Use Multimethods?
- •Adding Type Coercions to Lancet
- •Wrapping Up
- •Clojure in the Wild
- •Automating Tests
- •Data Access
- •Web Development
- •Farewell
- •Editor Support
- •Bibliography
- •Index
- •Symbols
MAKING LANCET TARGETS RUN ONLY ONCE 207
6.7 Making Lancet Targets Run Only Once
So far, you have worked on making Lancet able to invoke existing Ant tasks. Now it is time to think about how to implement a Lancet target, that is, a function that runs only once per build. Because we will build targets on top of functions, any Clojure function can become a target.
Targets are what makes Lancet dependency-based. Calling a target is the same as saying “I depend on this target.” Each target should execute only once, no matter how many times it is called. In other words:
1.The first caller of any target should execute the target.
2.All subsequent callers of a target should wait for the first caller to finish and then move on without calling the target again.
In this chapter, you have seen Clojure’s concurrency library: refs, atoms, agents, and vars. Another possibility is to dip down into Java and use the locking or atomic capabilities in Java itself. Let’s now consider each of these in turn to find the best approach for implementing targets.
•Refs provide coordinated, synchronous updates. At first glance, this looks like a good fit for targets. But Clojure transactions must be side effect free. Build tasks certainly have side effects, and retrying might be expensive or even incorrect, so rule out refs.
•Atoms cannot do the job alone. They provide no coordination and hence no way to wait for the first caller of a task to finish. We will use an atom to remember the return value of each target after it is called.
•Agents almost work. Every target could have an agent, and you could send-off the work of the target. Subsequent callers could await the completion of any targets they need. However, you cannot await one agent while in another agent. This prevents possible deadlocks and enforces the idea that agents are not a coordination mechanism. Rule out agents.
•Vars would unnecessarily limit Lancet to running on a single thread. Rule out vars.
Another possibility is good old-fashioned locking. It is perfectly fine to use locks in Clojure, if that is what your design calls for. Lancet needs to make coordinated, synchronous updates to impure functions, and this is a job for locks.
Prepared exclusively for WG Custom Motorcycles
Report erratum
this copy is (P1.0 printing, May 2009)
MAKING LANCET TARGETS RUN ONLY ONCE 208
At the REPL, create a function named create-runonce. This function will take an ordinary function and return a new function that will run only once.
Download lancet/step_3_repl.clj
Line 1 (defn create-runonce [function] 2 (let [sentinel (Object.)
3result (atom sentinel)]
4(fn [& args]
5(locking sentinel
6 |
(if (= @result |
sentinel) |
7 |
(reset! result (function)) |
|
8 |
@result))))) |
|
create-runonce works as follows:
•Line 2 creates a sentinel object. Whenever you see the sentinel value, you know that the target function has not yet run. The sentinel is a simple Java object so that it will never compare as equal to anything else.
•The result atom (line 3) remembers the return value from calling a target. It is initially set to sentinel, which means that the target has not run yet.
•Line 5 locks the sentinel. This guarantees that only one thread at a time can execute the code that follows.
•If the result is sentinel, then this is the first caller. Line 7 calls the function and reset!s the result.
•If the result is not sentinel, then this is not the first caller, so there is nothing to do but return the value of result.
Create a function named println-once that runs println only once, using create-runonce:
(def println-once (create-runonce println))
#'user/println-once
Now for the moment of truth. Call println-once twice.
> (println-once "there can be only one!") | there can be only one!
nil
user=> (println-once "there can be only one!")
nil
Prepared exclusively for WG Custom Motorcycles
Report erratum
this copy is (P1.0 printing, May 2009)
MAKING LANCET TARGETS RUN ONLY ONCE 209
Just to be on the safe side, verify that the original println still works, no matter how many times you call it:
(println "here") | here
nil
(println "still here") | still here
nil
Now that you have the core code working, let’s make it more usable. It would be nice to have a simple predicate to see whether a target has run. For testing, it would also be useful to be able to reset a target so that it will run again. Create a runonce function that returns three values:
•A has-run? predicate
•A reset-fn
•The runonce function itself
Download lancet/step_3_complete.clj
Line 1 (defn runonce
-"Create a function that will only run once. All other invocations
-return the first calculated value. The function can have side effects.
-Returns a [has-run-predicate, reset-fn, once-fn]"
5 [function]
-(let [sentinel (Object.)
-result (atom sentinel)
-reset-fn (fn [] (reset! result sentinel) nil)
-has-run? #(not= @result sentinel)]
10 [has-run?
-reset-fn
-(fn [& args]
-(locking sentinel
-(if (= @result sentinel)
15 |
(reset! result (function)) |
-@result)))]))
This is the same as the previous create-runonce, with two additions. reset-fn (line 8) simply sets the result back to sentinel, as if the target had never run. The has-run? predicate returns true if the result is not sentinel.
Notice that runonce encapsulates its implementation details. The sentinel and result objects are created in a local let and can never be accessed directly outside the function. Instead, you access them only through their “API,” the three functions returned by runonce. This is similar to
Prepared exclusively for WG Custom Motorcycles
Report erratum
this copy is (P1.0 printing, May 2009)
WRAPPING UP 210
using a private field in an OO language but is more flexible and more granular.
runonce creates a complete, albeit minimal, dependency system that can work with any Clojure function. The current feature set includes the following:
•Run-once semantics for any function
•A predicate to check a runonce function’s status
•A reset function for testing runonces
All that, and the entire codebase is less than two dozen lines.
Lancet Step 3: runonce
Download lancet/step_3.clj
(defn runonce
"Create a function that will only run once. All other invocations return the first calculated value. The function can have side effects. Returns a [has-run-predicate, reset-fn, once-fn]"
[function]
(let [sentinel (Object.) result (atom sentinel)
reset-fn (fn [] (reset! result sentinel)) has-run? #(not= @result sentinel)]
[has-run? reset-fn (fn [& args]
(locking sentinel
(if (= @result sentinel) (reset! result (function)) @result)))]))
6.8 Wrapping Up
Clojure’s concurrency model is the most innovative part of the language. The combination of software transactional memory, agents, atoms, and dynamic binding that you have seen in this chapter gives Clojure powerful abstractions for all sorts of concurrent systems. It also makes Clojure one of the few languages suited to the coming generation of multicore computer hardware.
But there’s still more. Clojure’s macro implementation is easy to learn and use correctly for common tasks and yet powerful enough for the harder macro-related tasks. In the next chapter, you will see how Clojure is bringing macros to mainstream programming.
Prepared exclusively for WG Custom Motorcycles
Report erratum
this copy is (P1.0 printing, May 2009)