My first Clojure macro May 26, 2013
Got my feet wet with Clojure
I started hardcode coding on Project Plugh. I'm working on moving a bunch of Lift concepts over to Clojure as I build Lift's comet facilities in Clojure so I can stream data to the browser.
Background… PartialFunction
In Scala, there's a PartialFunction
The key take-away for PartialFunctions is "... is a unary function where the domain does not necessarily include all values of type A."
The ability to test a PartialFunction
to see if the domain includes a particular value is very helpful. pf.isDefinedAt(x)
allows testing to see if the function is defined at a given value of x
.
But a PartialFunction
is a subclass of Function
, so PartialFunctions
can be applied:
pf(x)
The Scala compiler will take a pattern and turn it into a PartialFunction
:
def pf: PartialFunction[String, Number] =
{
case "" => 0 // special case blank to zero
case x if isInt(x) => x.toInt
case x if isDouble(x) => x.toDouble
case x if isBigInt(x) => asBigInt(x)
}
Another property of PartialFunction
is they can be composed:
pf = pf1 orElse pf2 orElse pf3 // pf isDefinedAt any place
// any of the partial functions are defined
We use PartialFunctions
extensively in Lift to allow choosing if a particular URL should be served by Lift, if it should be served by a particular REST handler, etc. For example, defining a REST route in Lift:
serve {
case "api" :: "user" :: AsLong(userId) :: _ GetJson _ =>
User.find(userId).map(_.toJson)
}
As I've been learning Clojure in preparation for a presentation at Strange Loop and as part of a new project I've been working on, I am looking to bring the best things in Lift into the Clojure code I write.
Into Clojure
Clojure's pattern matching stuff is pretty nifty. I especially like how you can extract values out of a Map (this is so much more powerful that Scala's pattern matching, even with unapply... but I digress).
So, I wrote a macro:
(defmacro match-func [& body] `(fn [~'x] (match [~'x] ~@body)))
This creates a function that is the application of the match to a parameter, so:
((match-func [q :guard even?] (+ 1 q) [z] (* 7 z)) 33)
;; 231
Turns out the Clojure pattern matcher will extract values into unbound variables. But bound variables are tested… this means that:
((match-func [[x y]] (+ x y)) [4 5])
Turns out this is a problem because x
is bound in the match-func
macro… so we need
to change x
to something else. So, we have to change
the variable x
to something else:
(defmacro match-func [& body]
"Create a function that does pattern matching."
`(fn [x#] (match [x#] ~@body)))
isDefinedAt
So, how do we test to see if the pattern matches at a particular value?
This became a challenge for me to wrap my brain around how things are done in Clojure. How do I have a function that represents the pattern match and be able to query it to see if it's defined at a point without invoking the computation on the right-side which can be side-effecting.
The answer is arity. Scala has functions with defined arity. Turns out that Clojure can have a single function that behaves differently depending on the arity of the invocation. Yay!
So, the macro looks like:
(defmacro match-pfunc [& body]
"Create a partial function that does pattern matching."
(let [rewrite (mapcat (fn [x] [(first x) true]) (partition 2 body))]
`(fn ([x#] (match [x#] ~@body))
([x# y#]
(cond
(= :defined? x#)
(match [y#] ~@rewrite)
(= :body x#)
'(~@body))))))
What this gives us is a function than can be invoked with a single parameter:
(pf 44)
And it can be invoked with 2 parameters:
(pf :defined? 44)
And I added the ability to get the body of the original so that I can add
an orElse
function that will actually build a new PartialFunction
that is the compilation of the composed patterns so that the patterns will
be compiled more efficiently.
First toe in the water
Yep. I think Clojure is pretty powerful. With macros, I've added one of the most amazingly powerful language feature of Scala to Clojure in a few lines.
The water feels pretty good so far.