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.