I still do not fully understand the difference between the two Clojure arrow macros thread-first -> and thread-last macro ->>.
While reading through https://clojure.org/guides/threading_macros I understand that thread first -> is an alternative expression for nested operations on a single object, each datastep using the object as input argument, performing an independent operation.
(defn transformation [object]
(transform2 (transform1 object)))
becomes
(defn transformation [object]
(-> object
(transform1) ;; object as implicit argument
(transform2))) ;; object as implicit argument
When using threat-last ->> operator, each tranformation uses output of the preceding transformation as implicit argument:
(defn transformation [object]
(->> object
(transform1) ;; object as implicit argument
(transform2))) ;; (transform1 object) as implicit argument
What are the practical implicantions of these differences? I understand that it makes sense to use threat -first -> for operations on maps and dictionaries, where each transformation creates a new copy of the original instance, which has to be supplied for the next operation.
However, in many cases, both operators actually behave the same:
(->> [2 5 4 1 3 6] (reverse) (rest) (sort) (count)) ;; => 5
(-> [2 5 4 1 3 6] (reverse) (rest) (sort) (count)) ;; => 5
The practical difference is where the macro "inserts" the variable (and the subsequent results) into the expressions:
(ns so.example)
(defn example-1 [s]
(-> s
(str "foo")))
(defn example-2 [s]
(->> s
(str "foo")))
(example-1 "bar")
;=> "barfoo"
(example-2 "bar")
;=> "foobar"
So (-> "bar" (str "foo")) is the same as (str "bar" "foo") and (->> "bar" (str "foo")) is the same as (str "foo" "bar"). With unary functions -> and ->> do the same thing.
When you need more flexibility as to where these results should be inserted, you would use as->:
(ns so.example)
(defn example-3 [s]
(as-> s v
(str "foo" v "foo")))
(example-3 "bar")
;=> "foobarfoo"
Following up on as-> as an answer purely because I can't format code in a comment.
Here's a usage of as-> from our codebase at work:
(-> date
(.getTime)
(as-> millis
(/ (- (.getTime (java.util.Date.)) millis)
1000 60 60 24))
(long)
(min days))
That computation could be unrolled, and ->> used to place the threaded value at the end of the - expression, but would probably be harder to read. It would also be possible to unroll it in such a way that -> alone would be enough (by negating the threaded value and then adding it to (.getTime (java.util.Date.))) but that would make it even harder to read I think.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With