London Clojure Dojo, December 2011

Apologies for lack of November post; I’ve been somewhat snowed under recently. Normal service now resuming…

December’s Clojure dojo focused on difficult problems from 4clojure. I had dabbled with 4clojure before on some of the easier problems, but I honestly hadn’t anticipated just how difficult the hard problems can get!

Our team decided to go in gently, going for medium-difficulty problems rather than hard problems. This turned out to be one of our better ideas of the evening, since we only managed to complete one and a half medium problems in the time available!

Juxtaposition

The first problem we tackled was Juxtaposition, in which you have to reimplement the juxt function. Our team took an approach where we tried to develop solutions from first principles, rather than looking up (source juxt), so I think it might be enlightening to compare our solutions with the clojure.core model answers. First, our solution:

(defn juxt [& fns]
        (fn [& args]
          (vec (map #(apply % args) fns))))

And the output of (source juxt) produces something like this:

(defn juxt [& fs]
     (fn [& args]
         (reduce #(conj %1 (apply %2 args)) [] fs)))

…except that the original source has special cases for small numbers of arguments.

It’s interesting the difference of approaches here. We both use the form (apply % args) to apply the variable number of arguments to each function in turn; however, we use map to do this, producing a sequence, which we then must traverse in order to convert to a vector.

The clojure.core version, by contrast, starts with an empty vector [], and conjes each further result into the vector; in doing so, it avoids traversing the list twice.

Reductions

The second problem we attempted was Reductions. Here’s our attempt:

(defn my-red
  ([f coll]
     (if (seq coll)
       (cons (first coll) (map #(f % (first coll)) (my-red f (rest (seq coll)))))
       []))
  ([f init coll]
     (my-red f (cons init coll))))

It performs well enough for bounded-length sequences:

(my-red + [1 2 3 4 5])
;=> (1 3 6 10 15)

But it fails when it comes to infinite lazy sequences:

(take 5 (my-red + (range)))
;=> StackOverflowError

We were very confused on the night as to why this should fail. Isn’t map lazy by default? Why, then, do we get a stack overflow?

The problem, which I have only found out today, 4 days after the event, is that map is a function, and therefore its arguments are evaluated before map itself is. Therefore, every call to my-red necessarily makes a recursive call, and thus exhausts the stack. The solution is to add a lazy-seq to the recursive call:

(defn my-red
  ([f coll]
     (if (seq coll)
       (cons (first coll) (map #(f % (first coll)) (lazy-seq (my-red f (rest (seq coll))))))
       []))
  ([f init coll]
     (my-red f (cons init coll))))

And thus, the previous example now works fine:

(take 5 (my-red + (range)))
;=> (0 1 3 6 10)

It still doesn’t pass all of the 4clojure unit tests, though. Work for another time, perhaps.

The clojure.core/reductions source looks like this:

(defn reductions
  "Returns a lazy seq of the intermediate values of the reduction (as
  per reduce) of coll by f, starting with init."
  {:added "1.2"}
  ([f coll]
     (lazy-seq
      (if-let [s (seq coll)]
        (reductions f (first s) (rest s))
        (list (f)))))
  ([f init coll]
     (cons init
           (lazy-seq
            (when-let [s (seq coll)]
              (reductions f (f init (first s)) (rest s)))))))

It’s a very similar approach to the problem, with some important differences:

  • It actually works (!)
  • It uses if-let and when-let with the seq function, relying on the behaviour that for empty sequences, seq returns nil, but also binding the returned seq simultaneously.
  • It treats the [f init coll] version as the primitive form and expresses [f coll] in terms of [f init coll]. We do it the other way, purely because we implemented [f coll] first. It’s ugly, though, particularly in cases where init is not the same type as members of coll.
  • It has special case handling for the (reductions f []) case — where an empty sequence is provided, it returns (list (f)).
  • I believe our use of map makes our solution quadratic rather than linear in the length of the input sequence, because the item in the nth position must be transformed by (n-1) fns and we don’t reuse the intermediate results like the clojure.core version does.

Summary

This has made me want to go back and give 4clojure a closer look. I had tried the first few problems, which seemed trivially easy, but now that I’ve seen that even the “Medium” problems present a significant challenge and raise all sorts of issues around laziness, algorithmic complexity, and efficiency, I can see I’ve a lot to learn from 4clojure.

The London clojure dojo happens on the last Tuesday of every month. During the dojo, we split into groups of four or five around a single computer, and each person takes a turn at the keyboard. This ensures that even if you have zero clojure experience, you will get the opportunity to write some code at the event.

Entry to the dojo is free, but advance booking is required. Listen for announcements on the London clojurians mailing list.

South East England Overtone Hack Day, 3rd December 2011

Today was the inaugural South East England Overtone Hack Day. We met up in Cambridge to hack on Overtone, a live music performance environment in Clojure. Here are some of the things we covered:

Audiocubes

Tom (didn’t catch his surname, unfortunately) brought some AudioCubes, a set of control interfaces. They have infrared detectors and can detect nearby surfaces and other AudioCubes.

We discussed interfacing them to Overtone. They come with midi and OSC interfaces, which would be easy to work with; but the real power apparently comes from the C API, which allows you to discover the network topology of the AudioCubes. We discussed the possibilities for working with the C API from Clojure.

Other controllers

Tom and I also discussed some other controllers:

  • TouchOSC
    • very low barrier to entry
    • not nice to rely on wireless connections in a live environment
  • Launchpad
    • cost effective introduction to real hardware

Environment setup

As is inevitable, some time was spent getting people set up with Eclipse, Counterclockwise, leiningen, overtone, and so on. I made use of my previous blogpost on lein eclipse, which i had totally forgotten about…

Overtone basics

I went through my skillsmatter talk with Stefan, Tak and Edmund, to show them the basics of creating instruments, oscillators, and filters; and scheduling beats and tunes in time.

Signal processing basics

We also had an impromptu introduction to signal processing – time domain vs frequency domain, linear filters — low pass, high pass, band pass, Fourier series, and suchlike.

Clojure basics

Finally, we discussed resources for learning clojure itself: labrepl and 4clojure.

Overtone documentation

I also made a start on writing a filters page for the overtone wiki.

Summary

We all had a great time, we all learned something and achieved something, and there was a lot of interest for another event next month in London. So I will see you all next time!

Opening a leiningen project in eclipse

I was wanting to play around with counterclockwise, the eclipse plugin for clojure, recently, when I got stuck trying to open an existing leiningen project in eclipse. If I were coding Java, I’d have no trouble with the analagous problem of importing a maven project into IntelliJ, but I struggled a bit with this one enough that I thought I’d miniblog it so I’d remember in future.

This post assumes you have Leiningen, eclipse and counterclockwise installed.

Install the lein-eclipse plugin. (You may want to check the latest version on clojars. As of writing, there seems to be a rival 1.1.0 from robertrolandorg; I’m not sure of the difference.

$ lein plugin install lein-eclipse 1.0.0
Including lein-eclipse-1.0.0.jar
Created lein-eclipse-1.0.0.jar

Run lein eclipse to create the files eclipse needs:

$ lein eclipse
Copying 15 files to /Users/philippotter/src/mobile/jquery-mobile-experiment/lib
Created .classpath
Created .project

Then, in eclipse, do “File->Import->Existing Project into Workspace”. You should now have your project imported.

Evangelizing Clojure

I’m currently attending the second annual Clojure Conj, the premiere Clojure-specific conference. One of the themes that has been emerging is evangelizing Clojure: getting more people to use it, and convincing people to use Clojure who otherwise wouldn’t choose to use it.

This was in fact the central theme of Neal Ford’s talk “Neal’s Master Plan for Clojure Enterprise Mindshare Domination”, in which he put forward his ideas for how to get large organizations full of institutional inertia to adopt Clojure. Phil Bagwell also made reference to this in the introduction to his talk, in which he asked everyone using Clojure in their day job to put their hand up, then asked everyone who’s never deployed Clojure to production to put their hand up, then asked group 2 to talk to the nearest person in group 1 and ask them how they got to work in Clojure.

Clojure evangelism has also been a common theme of Q&A sessions after talks: a talk on ClojureScript will often be followed with a question such as “How do you fit this into an existing JavaScript project?”

There are a few key themes emerging:

Know your enemy

There’s a lot of competition amongst the new language communities, particularly between Scala, Clojure, and Groovy. This is absolutely fine and as it should be. Furthermore, if Scala is successful, this is in no way directly detrimental to Clojure.

Scala, Clojure and Groovy are in competition, but they are not enemies of each other. The real enemy is the status quo. It is the nasty feeling that people have when they say things like:

  • “Taking on Clojure is a big risk – I want to be certain”
  • “I think Clojure might be a better choice than Java, but because Java is an industry standard, I am more likely to get blamed if I choose Clojure and the project fails.”
  • “If I choose Clojure, I don’t know I’ll be able to hire developers who know it”

Ultimately, these statements reflect a sentiment that staying with the same old technology is safer than trying to improve productivity by choosing a newer, but less well-known, technology.

If a client has switched from Java to Groovy, Scala, or JRuby, they have already rejected the status quo. Encouraging an environment in which people feel able to explore new technologies will make more people who are interested in Clojure overcome their fears and try it out. In other words, a rising tide raises all ships.

Be positive about the new possibilities, not negative about the status quo

By and large the feeling of the conference has been upbeat and positive, rather than tribal. That is why, when a couple of off-hand jokes about Ruby were made, people immediately called it out as nonconstructive.

Clojure and Scala in particular are languages which make many things possible which simply aren’t possible in other languages. This can lead to a feeling of superiority. Fight that feeling! Clojure is not going to gain mindshare by denigrating Ruby and Java; it is going to gain mindshare by promoting itself and solving problems effectively.

Furthermore, there are many problem domains where the existing tools are entirely appropriate — Rails is a fine framework, and although it has limitations, those limitations don’t manifest in most use cases. Even some of the clojure.core team use Rails for most of their work, and Clojure only for the difficult problems. Denigrating Rails builds walls, when we want to be building bridges.

Build grassroots through the back door

Many recent successes in language proliferation have been achieved simply by providing great tools in those languages. Even if someone doesn’t particularly want Ruby, they might well want Cucumber. If they don’t want Groovy, they might still want Gradle.

On my current project, we’re using node.js for a test stub, even though none of us is a particular JavaScript advocate. Node was just the best tool for the job, so we used it. But that’s caused a lot of us to look at JavaScript in a new way, and I’d say we’re all more likely to use JavaScript again in future as a result.

This is a great way to build up mindshare. I think one tool I’ve learned about at the conj which could fill this role is pulse from Heroku, described in Mark McGranaghan’s “Logs as Data” talk. Pulse is a tool for processing logs not as a stream of bytes but as a stream of events and rich data objects. Another is Cascalog, from Nathan Marz at Twitter, which is a high-level abstraction over Hadoop MapReduce, which creates a very nice internal DSL for modelling MapReduce computations as queries and predicates.

In summary

It’s been a really exciting conference for me, because Clojure is both a great language, and at a key point in its history. It has reached maturity, it is being used by a few people in production to solve interesting and difficult problems; but the next step is to evangelize Clojure, to get it used in earnest by more and more people.

Happy hacking!

Learning monads in Clojure: a warning

I was inspired to learn about monads by Chris Ford recently; his description of encapsulating impurity safely within a pure language had me intrigued immediately. I decided that I wanted to learn about monads in Clojure, a language I am currently diving into.

However, I found learning about monads in Clojure full of fake difficulty (or accidental complexity, if you will). Here I document the issues I found. And the key issue I came across was this:

Learning monads requires reasoning about types

You probably know where I’m going with this. Clojure is dynamically typed. Haskell, the spiritual home of monads, is statically typed. For me, the key to understanding monads was reasoning about types — in particular, drawing a clear distinction between the ordinary type and the type of a monadic expression.

In drawing this distinction, it helped me reason about the behaviour of the monadic functions. By learning that m-bind must return a monadic expression and not a simple value, I learned a key fact about monads; but the number of times I tried to write m-bind expressions beforehand which did not return monadic expressions beforehand was too many.

It’s quite possible to reason about types in a dynamically typed language, but it’s made much harder. If your reasoning is faulty, the program will try to carry on regardless, and in Clojure’s case, give an incredibly cryptic error message. This is not an environment that makes learning easy. If I had been learning in Haskell, my failure to understand the distinction between monadic expression and ordinary value would have immediately been set right by the type checker.

But it’s worse than just making learning hard: Clojure’s dynamic typing has led to a pervasive failure of type reasoning.

A key example of this is that Clojure’s implementation of the maybe monad, maybe-m, breaks the monad laws! It does this because it does not properly distinguish between the monadic expression and the underlying type. The law in question is the first monad law, expressed here as a Midje test:

;;; given a monad which defines m-bind and m-result,
;;;       f, an arbitrary function, and
;;;       val, an arbitrary value
(fact "The first monad law"
    (m-bind (m-result val) f)
    => (f val))

The failure of maybe-m to adhere to this law is demonstrated thus:

;;; failing midje test
(fact "maybe-m should adhere to the first monad law"
    (with-monad maybe-m
        (m-bind (m-result nil) not))
    => (not nil))

The reason that this law is violated is that the maybe-m monadic expression type is no different from the underlying value type. It is therefore possible to find a value such that (m-result val) is nil, the maybe monad’s value for failure.

The Haskell Maybe monad is not so sloppy:

> let myNot x = Just (x == Nothing)
> (return Nothing :: Maybe (Maybe Char)) >>= myNot
Just True
> myNot (Nothing :: Maybe (Maybe Char))
Just True

This is because in Haskell, there is no value foo such that Nothing == return foo; in Clojure, there is such a value: (= nil (m-result nil)).

The repercussions of maybe-m’s violation of the first monad law are relatively minor: it means that when using maybe-m, the value nil has been appropriated and given a new meaning; which means that if you had any other meaning for it, you’re stuffed.

For example, suppose you wanted to implement a distributed hash table retrieval, where failure could be caused by a network outage. You want a function behaviour similar to (get {:a 1} :b), where if the value is not in the table you return nil. If you use maybe-m to perform this calculation, you cannot tell the difference between failing to communicate with the DHT, and successfully determining that the DHT does not contain anything under the key :b; both will result in the value nil. Worse, if you want to use this value later in the computation, the maybe-m will assume a value missing in the DHT to be a failure, and cut your computation short — even if that’s not what you wanted.

Summary

If you want to learn monads, do it in Haskell.

If you must do it in Clojure, the key is to understand and distinguish the various types in play. The monadic type is distinct from the underlying type. m-result takes an underlying value and gives you an equivalent value in the monadic type. m-bind takes a monadic value, and a function from an underlying value to a monadic value.

Chestnut Roast

I recently found out that a vegetarian friend of mine had never heard of chestnut roast! Although I am a massive carnivore now, I was in fact a vegetarian for five years, and during this time I discovered that chestnut roast is possibly the best vegetarian dish there is. I mentioned it in passing to my fiancée, who immediately suggested that I could cook it for her. I should learn to keep my damn mouth shut.

ingredients

  • 240g chestnuts, in one of those weird vacuum-sealed tins
  • 150g cashews
  • 4 closed cup mushrooms
  • 120g goat’s cheese
  • sage
  • tarragon
  • parsley
  • 2 slices granary bread, turned into crumbs
  • small amount of veg stock
  • 2 onions
  • knob of butter

Method

Heat the butter in a frying pan, then chop and fry the onions. After a while, chop and add the mushrooms.

Meanwhile, chop/bash/whizz the cashews. Add the chestnuts and whizz or mash.

When the onions and mushrooms are done, add to the nut mix and stir well. Add the goat’s cheese, veg stock, chopped parsley, sage and tarragon, and breadcrumbs.

Roast in a pre-heated oven at 200 °C for an hour.

Serve with potatoes and veg. We had mash and peas.

Results

Delicious.

chestnut roast

more chestnut roast

even more chestnut roast

I think next time I’d ditch the tarragon, as it tends to overpower the rest of the flavours. Also, I think I have too many things competing with the chestnuts — the mushrooms and breadcrumbs are probably diluting the flavour too much. This is a delicious meal, and one I had for christmas dinner when I was a vegetarian. It takes a fair amount of prep work and a long cooking time, so it’s not going to become a normal after-work evening meal for me, but I’ll keep it in my repertoire for special occasions.

London Clojure Dojo, September 2011

The latest clojure dojo was on a subject suggested by Robert Rees: write a program to play 20 Questions, such that the program starts with a small dataset of known celebrities, but each time it fails to guess someone, you teach it a new question. So, for example, if it had guessed Barack Obama but I was thinking of Abraham Lincoln, I could teach it to ask “Are you alive?” to distinguish between these two people later.

I decided to take the idea and extend it by writing a shared server which everyone could use. They would download the initial dataset, play a game locally, then when they were finished, upload their dataset back to the server to share with everyone else.

The code for the server is on my github. It operates via simple HTTP — there is one URL at /20-questions/latest which accepts either a GET request to pull down the data, or a PUT to push it back up.

Once again, I learned a number of lessons — last time, they were about participating in a dojo, this time, they were about organizing a dojo.

Lesson 1: User input is hard.

It’s upsetting that I hadn’t thought about this beforehand, but just like in every other language, user input in Clojure is painful, and difficult to get anything done with in the time constraints of a dojo. Although one or two of the dojo teams managed to wrestle with read-line enough to get it working, the rest of the teams ended up writing internal DSLs (or, less grandiosely, functions) to play the game without having to solve some accidental complexity. In hindsight, I might have thought more deeply about how to deal with user input.

Lesson 2: Error handling feedback is important

A number of the teams had problems uploading to the server. Uploading is always going to be harder than downloading, because you have to generate the data in the correct format, and there’s more ways for that to go wrong than for parsing the downloaded data.

The server had actually been set up to issue useful responses to problems: if it thought a request was not well formatted, or that it didn’t resemble a tree of questions, it would return a 400 (Bad request) code. If, on the other hand, it received a valid question tree, but one which was based on out of date data, it would return a 409 (Conflict) response. An example of the latter situation is where you try to upload a tree, but somebody else adds a new celebrity and beats you to it. In order to prevent people from wiping out each other’s progress, the server would reject the request and issue a 409.

Two problems prevented this from being useful feedback. The first was that I just didn’t explain this at all; I felt that it might be too much detail to go in before people had gotten their teeth into it. But I could have at least mentioned that the error codes were meaningful and that they could have asked me for help once they had reached the point of trying to upload.

The second problem was that clj-http, the library we were using, did not show very much feedback when the error responses were received. The server had, in fact, filled in a full request body, explaining the situation in plain english; but clj-http just threw an exception which said “400” or “409” – not even a “Bad request” or “Conflict”! This made the fact that there was more information available very undiscoverable.

So all in all, as I could see these problems happening, people struggling with user input or seemingly random HTTP response codes, I was getting more and more nervous that nobody would be able to upload anything to the central server — whose data, by the way, was being projected onto a big screen for everyone to see. Thankfully, after a while, one of the teams successfully uploaded, and shortly thereafter, a number of the other teams did too. By the time we were ready for show and tell, we had built up quite a question tree:

{:no
 {:no
  {:no "Elizabeth I",
   :question "Did you find Nemo?",
   :yes "Nemo's Mum"},
  :question "Are you a king?",
  :yes
  {:no "Richard the 3rd",
   :question "Do you have blue suede shoes",
   :yes "Elvis"}},
 :question "Are you alive?",
 :yes
 {:no
  {:no
   {:no
    {:no
     {:no "Chuck Norris",
      :question "Are you in the building?",
      :yes "Elvis"},
     :question "Who's the Daddy?",
     :yes "The Daddy"},
    :question "Are you a panda?",
    :yes
    {:no "Kung fu Panda",
     :question "Are you ling ling",
     :yes "Ling Ling"}},
   :question "Are you a kung fu expert?",
   :yes
   {:no "Bruce Durling",
    :question "Are you (not Bruce Durling)?",
    :yes "Not Bruce Durling"}},
  :question "are you a newsreader?",
  :yes
  {:no "Trevor McDonald",
   :question "Are you the Irish newsreader (the only one)?",
   :yes "Sharon Ni Bheolan"}}}

I think I’ve never seen such a great example of the GIGO principle.

If you want to see some of the client code, team 4 and team 5 uploaded theirs. Also, after the dojo, Uday created a zipper-based client.

The London clojure dojo happens on the last Tuesday of every month. During the dojo, we split into groups of four or five around a single computer, and each person takes a turn at the keyboard. This ensures that even if you have zero clojure experience, you will get the opportunity to write some code at the event.

Entry to the dojo is free, but advance booking is required. Listen for announcements on the London clojurians mailing list.

London Clojure Dojo, August 2011

Prologue: I’m going to start blogging every month’s London clojure dojo here. For more details, see epilogue.

August’s dojo, like most of them, was a bit of an experiment. The wonderful Dale Thatcher had prepared a tournament environment for all of our clojure programs to compete in.

The format was this: every ten minutes, we had to upload a working clojure program to a tournament server. The server would then play each team’s program against every other program and determine the winner each time. Points would be scored and added to a running leaderboard.

After settling in with a few rounds of rock, paper, scissors, we started the main feature: noughts and crosses! Your program is given a command line argument of the form

xx0-0----

representing a noughts and crosses grid. You have to output a number from the range 0-8 indicating your next move. One snag is that you are not told whether you are noughts or crosses, and have to determine this fact with the knowledge that noughts go first.

So for example, in the above board layout, you would instantly win if you output 6, because you are noughts, and you would have formed a line top-right-to-bottom-left.

I was on team 4 — you can grab the code and follow on from github.

Lesson 1 — automate common tasks

Since we were to be uploading a new contender every 10 minutes, the first thing we did was write a script called gen-zip which did just that. This script bundled our app into a zip file and uploaded our entry to the tournament server in one step. Hey presto, continuous delivery! Furthermore, this script also automatically committed to git, keeping a history of every single program we uploaded to the tournament server (from the second round onwards, at least).

Lesson 2 — start simple and evolve

Our first entry for noughts and crosses was simply (println 5). It was basically the fastest thing we could write which wasn’t simply guaranteed to lose. Since deployment is cheap, we uploaded it, although it wasn’t captured in the git history. Our second entry (aed257) simply searched for the first free space and went there:

(defn choice [board]
  (inc (.indexOf board "-")))

The simplicity of this is astonishing:

  • no need to parse the board string into an actual board object
  • no need to determine which player we are

Instead, we just use Java’s native String.indexOf() method to find the first free space and output the index corresponding to that space.

Our next contender (2db1fc1) was only slightly more complex:

(defn choice [board]
  (cond (= \- (nth board 4)) 5
    (= \- (nth board 0)) 0
    (= \- (nth board 6)) 6
    (= \- (nth board 2)) 2
    (= \- (nth board 8)) 8
    :else (inc (.indexOf board "-"))))

Here we use the fact that clojure strings are sequences and can have nth called on them just like anything else. Once more, we don’t need to parse the string into a board; we work only with the flattened string, and we add a bunch of special cases to make a brute-force strategy of getting the middle and then each corner in turn. If all are taken, we revert to our original strategy of the first available space.

Lesson 3a — understand the problem before rushing to a solution

Lesson 3b — get frequent, rapid feedback

You may have noticed some oddities in the above code. What is that inc doing there before .indexOf? Why do we say (cond (= \- (nth board 4)) 5 ...) not (cond (= \- (nth board 4)) 4 ...)? This is because we made the faulty assumption that the grid indexing was 1-based, not 0-based. As a result, we were often going one square to the right of where we actually had intended to go!

We only discovered this because one member of our team was checking each tournament result to see which other teams we lost to and why. He noticed that we were making invalid moves — something we thought we had avoided by always aiming for an empty space.

Our next version (d83b9a6c) fixed this problem. It also started work on a significant new effort — parsing the board and detecting if it was possible to win outright from the current position.

Lesson 4 — doing something simple correctly is better than doing something sophisticated but wrong

By this time our work on instant-win to determine if there was an instant winner was the most significant part of our efforts. The next step was a function to determine which player we were. This was deemed a challenging enough task that we wrote a test for the function. Next we wrote a test for our instant win function, and aimed towards putting together the pieces which could make that function pass. However, all of this was a waste, because we simply didn’t have the time or phenomenal brains required to solve the problem in the half hour we had remaining.

By contrast, the only significant refinements we made to our program in these rounds were based on feedback from tournament results:

  • in 8e4e524, grabbing square 2 earlier, to stop us losing to those enemy programs who just filled the grid in order (as our example in aed257 had done).
  • in 244c97c, changing from 4-2-0-6-8 to 4-2-6-0-8 to fill the 2-4-6 line one move faster.

Our final version had reams of abortive attempts at sophistication, all commented out using the #_ reader macro. But the real reward had come from those old tactics, rapid feedback and gradual evolution.

Epilogue : the London clojure dojo happens on the last Tuesday of every month. During the dojo, we split into groups of four or five around a single computer, and each person takes a turn at the keyboard. This ensures that even if you have zero clojure experience, you will get the opportunity to write some code at the event.

Entry to the dojo is free, but advance booking is required. Listen for announcements on the London clojurians mailing list.

OSS contributions

In the spirit of define-measure-improve, here is a list of open source projects I have contributed to, and the largest individual contribution to each project.

Hopefully maintaining this list will encourage me to improve my open source contributions.

"If you can not measure it, you can not improve it." -- Lord Kelvin