Syntactically, the threading macro also allows the reader to read the functions in left to right order of application, rather than reading from the innermost expression out.

Semantically, transform* is equivalent to transform : the arrow macro expands at compile time into the original code. In each case, the return value of the function is the result of the last computation, the call to update . The re-written function reads like a description of the transformation: "Take a person, increase their age, give them gray hair, and return the result". Of course in the context of immutable values, no actual mutation takes place. Instead, the function simply returns a new value with updated attributes.

Though not often seen in practice, this visual aid is valid Clojure syntax, as commas are whitespace in Clojure.

Starting with the second form, the macro inserts the first value as its first argument. This is repeated at each subsequent step with the result of the previous computation inserted as the first argument of the next form. What looks like a function call with two arguments is in fact a call with three arguments, as the threaded value is inserted just after the function name. It may be helpful to mark the insertion point with three commas for illustration:

Note: The word "thread" in this context (meaning passing a value through a pipeline of functions) is unrelated to the concept of concurrent threads of execution.

Taking an initial value as its first argument, -> threads it through one or more expressions.

transform is an example of a common pattern: it takes a value and applies multiple transformations with each step in the pipeline taking the result of the previous step as its input. It is often possible to improve code of this type by rewriting it to use the thread-first macro -> :

thread-last (->>) and thread-as (as->) macros

The -> macro follows a purely syntactic transformation rule: for each expression, insert the threaded value between the function name and the first argument. Note that the threading expressions are function calls of the form (f arg1 arg2 …​) . A bare symbol or keyword without parentheses is interpreted as a simple function invocation with a single argument. This allows for a succinct chain of unary functions:

(-> person :hair-color name clojure.string/upper-case) ;; equivalent to (-> person (:hair-color) (name) (clojure.string/upper-case))

However, -> is not universally applicable, as we do not always want to insert the threaded argument in the initial position. Consider a function that computes the sum of the squares of all odd positive integers below ten:

(defn calculate [] (reduce + (map #(* % %) (filter odd? (range 10)))))

Like transform , calculate is a pipeline of transformations, but unlike the former, the threaded value appears in each function call in the final position in the argument list. Instead of the thread-first macro we need to use the thread-last macro ->> instead:

(defn calculate* [] (->> (range 10) (filter odd? ,,,) (map #(* % %) ,,,) (reduce + ,,,)))

Again, though usually omitted, three commas mark the place where the argument will be inserted. As you can see, in forms threaded using ->> the threaded value is inserted at the end rather than the beginning of the argument list.

Thread-first and thread-last are used in different circumstances. Which one is appropriate depends on the signature of the transformation functions. Ultimately you’ll need to consult the documentation of the functions used, but there are a few rules of thumb:

By convention, core functions that operate on sequences expect the sequence as their last argument. Accordingly, pipelines containing map , filter , remove , reduce , into , etc usually call for the ->> macro.

Core functions that operate on data structures, on the other hand, expect the value they work on as their first argument. These include assoc , update , dissoc , get and their -in variants. Pipelines that transform maps using these functions often require the -> macro.

When calling methods through Java interop, the Java object is passed in as the first argument. In such cases, -> is useful, for example, to check a string for a prefix: (-> a-string clojure.string/lower-case (.startsWith "prefix")) Note also the more specialized interop macros .. and doto .

Finally, there are cases where neither -> nor ->> are applicable. A pipeline may consist of function calls with varying insertion points. In these cases, you’ll need to use as-> , the more flexible alternative. as-> expects two fixed arguments and a variable number of expressions. As with -> , the first argument is a value to be threaded through the following forms. The second argument is the name of a binding. In each of the subsequent forms, the bound name can be used for the prior expression’s result. This allows a value to thread into any argument position, not just first or last.