To help myself learn macros, I made a custom version of the threading macro that lets you choose which argument the "thread" gets put into.
My main concern is simplifying it, and making it more idiomatic, but anything notable is welcome. If anyone knows of a better implementation for insert-at
, I'd love to know that as well!
; Helpers. Other macros use these, or else I'd just inline them
(defn factor? [n m]
(= (rem n m) 0))
(defn mult-forms? [n forms]
(factor? (count forms) n))
(defn insert-at
"Inserts an element at the given index. Returns a list.
Indices out of range of the collection are treated as either 0 or (dec (count xs))
Likely very expensive."
[xs x index]
(let [[f-half s-half] (split-at index xs)]
(concat f-half `(~x) s-half)))
(defmacro n->
"Expects a value to be threaded, and pairs of indices and forms.
The indices indicate which index the value should be inserted at in the following form.
Example:
(n-> 1
1 (+ 2)
0 (+ 3))
Expands to: (+ (+ 2 1) 3)"
[val & forms]
(if (and (not (empty? forms))
(mult-forms? 2 forms))
(let [[thread-n form & rest-forms] forms
threaded (insert-at form val (inc thread-n))]
`(n-> ~threaded ~@rest-forms))
val))
Note, I'm not claiming this is a useful macro; it will likely lead to confusing to read code. This was just an exercise.
Example:
(n-> "Hello"
0 (str "0" "1" "2" "3")
1 (str "0" "1" "2" "3")
2 (str "0" "1" "2" "3"))
Expands to:
(clojure.core/str "0" "1"
(clojure.core/str "0"
(clojure.core/str "Hello" "0" "1" "2" "3") "1" "2" "3") "2" "3")
Which evaluates to:
"010Hello012312323"