Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clojure thread-first macro (->) vs thread-last macro (->>) What are the practical differences?

Tags:

clojure

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
like image 317
GitCrush Avatar asked Oct 20 '25 14:10

GitCrush


2 Answers

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"
like image 72
customcommander Avatar answered Oct 22 '25 04:10

customcommander


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.

like image 21
Sean Corfield Avatar answered Oct 22 '25 04:10

Sean Corfield



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!