In my last post I had mentioned that there could be an improvement to
the code below:
(-> subject-items ((partial sort-by :Name)) ((partial drop (* page-number items-per-page)) ((partial take items-per-page)))
Simple method chain taking advantage of partial methods. That might
be fine for lesser beings, but not for someone so ruggishly handsome
To take an easy example that can be run in the REPL:
(-> (range 20) ((partial drop 5)) ((partial take 5)) ((partial apply +)))
Create a list of 1 to 20, drop the first 5, take the next 5, and add
them. You know, completely useless in the real world. What irks me
(Yes I used the word irks) is:
((partial ... )).
The reason for the double parenthesis is that as the chain moves to
the next step, it applies the chain result to it. If it were only:
(partial drop 5 [1 2 ... 20])
it would have tried to apply the range list to the partial call.
Uh oh… not good. Now look at it from the correct way:
((partial drop 5) [1 2 ...20])
Now it actually works. Partial is created, and THEN the list is
applied to it. Weeee!
So what’s next? Removing the partial thing. What I want, and you
should want too, is this:
(somemacroname (range 20) (drop 5) (take 5) (apply +))
A fair bit less typing, and less words to read. Win and Win.
Now when it comes to macros, It’s a goog idea to remember that macros,
for the most part, is just about assembling lists. After all, Clojure
is a part of the Lisp family. Therefore, there is no difference
between a list, or code.
So to get underway, what do we need? A really cool, and novel name
for the macro.
Next, there needs to be a parameter list for the macro to use. There
are two parts of the original code: The list, and the list
manipulators. Because the list, in this context, is the odd ball, it
gets its own parameter. The manipulators get smashed into one group.
Don’t cry for them. Not them.
(defmacro |-> [original & items]
That way easy. Time to go home.
Ok so that I’m home, I’ll move to the next step:
(defmacro |-> [original & items] `(-> ~original )
That the -> an obvious given. The -> is needed. The
~original takes the value in original (range 20), and will resolve it
at expansion time.
Why not just:
(defmacro |-> [original & items] `(-> original )
Because if you expand that, the word ‘original’ will just be there,
not the (range 0 20) that it represents.
(-> (range 0 20)
original (with no ~) produces:
Next step is to figure out how to get this:
((partial drop 5)) ((partial take 5)) ((partial apply +))
(drop 5) (take 5) (apply +)
Remember, we are creating code by manipulating lists. What we need is
a tranformation from (drop 5) to ((partial drop 5)). How can this be
(cons `partial %)
Ok, why cons? Every item in the items list is itself a list.
If “cons” the keyword partial to it I get:
('partial drop 5)
Essentially I am making a new list by attaching partial to the old
one. However, that doesn’t get what is needed.
((partial drop 5))
So guess what? Cons again.
(cons (cons 'partial %) '())
This time I consed the new partial/drop list to an empty list.
If only there was a way to automate the creation of a list of the
(map #(cons (cons 'partial %) '()) items )
Now to put it all together:
(defmacro |-> [original & items] `(-> ~original ~@(map #(cons (cons 'partial %) '()) items )))
The ~@ is just so that it evalates the map call/result at expansion
time. The @ gets rid of the surrounding parenthesis of the list that
Now you do exciting things like:
(|-> (range 0 20) (drop 5) (take 5) (map #(+ 1 %)))