jeemar
jeemar

Reputation: 548

Clojure threading macro

I'm not sure this is the best place to post, but why doesn't 2) work? Doesn't the threading macro pass the result of seq into (map str)?

;; 1
(map str (seq (str (* 8 8)))) -> ("6" "4")

;; 2
(defn a [x y]
  (-> (* x y)
      str
      seq
      (map str)))


(a 8 8) -> Don't know how to create ISeq from: clojure.core$str

Upvotes: 4

Views: 603

Answers (4)

Alan Thompson
Alan Thompson

Reputation: 29958

You might also consider using the it-> threading form from the Tupelo library, which is designed to avoid thread-first vs thread-last worries. Consider this code:

(ns clj.core
  (:use tupelo.core))

(defn a [x y]
  (it-> (* x y)
      (str it)
      (seq it)
      (map str it)))

(spyx (a 8 8))

(defn -main [] )

Which produces:

(a 8 8) => ("6" "4")

The it-> macro uses the placeholder it to explicitly choose where to place the result of each successive form. This is an easy way to simplify code and avoid this type of error.

Upvotes: 2

Adam Lee
Adam Lee

Reputation: 1894

Here is an alternative that is good to know (it's read from right to left):

(defn a [x y]
  ((comp (partial map str) seq str *)
    x y))

This might be useful in contexts where you want to separate the transformations to apply, from the data they're applied to (I would welcome criticism on the subject 😃).

Upvotes: 2

Arthur Ulfeldt
Arthur Ulfeldt

Reputation: 91554

as Elogent points out the macro is putting the arguments in the wrong place. In general when working with macros (and especially writing them) it helps to know about macroexpand-1 and combine it with clojure.pprint/pprint to get a clear view of what is actually running:

user> (clojure.pprint/pprint
       (macroexpand-1
        '(-> (* x y)
             str
             seq
             (map str))))
(map (seq (str (* x y))) str)
nil

Which we can see doesn't quite look right. So next we fiddle with it until it expands to what we expect:

user> (clojure.pprint/pprint
       (macroexpand-1
        '(->> (* x y)
              str
              seq
              (map str))))
(map str (seq (str (* x y))))
nil

There are a variety of threading macros to help with situations like this, especially as-> which lets you give an explicit name to the threaded value when you need to thread functions that alternate using the first and last argument for the important input:

user> (as-> (* 7 42) x
        (str x)
        (seq x)
        (map str x))
("2" "9" "4")

Upvotes: 7

Sam Estep
Sam Estep

Reputation: 13324

You're using the thread-first macro, which inserts each form as the second item of the next, like this:

(-> (* x y) str seq (map str))
(-> (str (* x y)) seq (map str))
(-> (seq (str (* x y))) (map str))
(map (seq (str (* x y))) str)

What you want is the thread-last macro:

(defn a [x y]
  (->> (* x y) str seq (map str)))

(a 8 8) ;=> ("6" "4")

Upvotes: 9

Related Questions