Pat Shields

Clojure Bootcamp Part 1

24 Feb 2014

Adzerk has been a polyglot programming shop for as long as anyone can remember. We have code written in C#, Ruby, Coffeescript, Java, and I have a tiny bit of python stashed away somewhere. A few months ago, I added Clojure to that list when I wrote two small services we needed. They’ve been operationally painless and have performed well, particularly given that I they have not been tuned at all. Given my success there and the potential for future projects in Clojure, the team decided to learn Clojure and asked me to teach them.

Since then we’ve done two weeks of "Clojure bootcamp.” Each week we have a small project to work on and we get together on Fridays and compare solutions. During the week we use a dedicated Hipchat room and a few face to face conversations to make sure no one gets stuck. My hope is that after six weeks, everyone will be prepared with some base knowledge of Clojure and how to write it idiomatically. Along the way, I’ve decided to share some of my experiences as a sort of “teacher” in this environment. I’ve been surprised by a number of happenings and thought that they might be helpful to others who are bringing Clojure into their team.

For the sake of science and background I’ll note here that there are five people in the group (three of whom, including myself, would be described as senior). All members are familiar with functional constructs via languages like Javascript and Ruby, but I would say that, in general, the group is more comfortable with OOP.

Conciseness

Most of the group was very concerned about conciseness and repetition. Consider the following code:

(defmulti btc-doge-convert :type)
(defmethod btc-doge-convert :bitcoin
  [currency]
  {:type :dogecoin
   :amount (* (:amount currency) doge-to-btc-ratio)})
(defmethod btc-doge-convert :dogecoin
  [currency]
  {:type :bitcoin
   :amount (/ (:amount currency) doge-to-btc-ratio)})

The example was meant as a first introduction to multimethods. Multiple people commented that they wished they had a way to remove the repeated construction of maps. While I can see a number of ways to shorten this, I'm not sure that any of them would make it particularly more clear. There is probably some argument for maintainability in the face of schema changes (e.g. renaming :type to :currency-type), but within the scope of the example this seems overkill.

My editorial take on this is that we, as programmers, tend to exalt conciseness as a goal unto itself. Conciseness is only good as much as it is an indicator of greater expressiveness and clarity. Instead of making it look like code golf, we should be focusing on the fact that concision happens because of the expressivity of function composition allows us to do more with less.

Accessing map values

Clojure provides a lot of different options for accessing map elements:

(get {:a 1} :a) ; 1
(:a {:a 1}) ; 1
({:a 1} :a) ; 1

These are convenient, but to newcomers this can be a little hard to understand, or even obfuscating. The concept that keywords or maps can act as functions is difficult to take in, particularly if you are just wrapping your head around s-expressions. The bigger problem is that this sugar doesn't always work exactly the same way as using, say, get.

(defn get-from-map [map key] (key map))
(get-from-map {:a 1} :a) ; 1 - Proof by example

;; Unfortunately, we don't see this test case
(get-from-map {"a" 1} "a") ; ClassCastException

We can conjure plenty of examples like this. We've discussed this as a group, and until the team gets a better understanding of the semantics behind the sugar, we'll be sticking to get, particularly when the key is constant. Much of my actual production code ends up relying on prismatic's safe-get so I might as well learn towards uniformity.

Functional idioms

I've mostly talked about sticking points for the group. One thing that has pleasantly surprised me is that no one in the group seems to have fallen into the trap of ignoring many of the built-in functions. My early experiments with Clojure were filled with far to many loop/recur expressions because I simply didn't know that a function existed that handled my requirements. My experiences writing in other functional languages had not prepared me for extensivity of the Clojure standard library. I was cured of this mostly by reading the Clojure code of others and the discovery of clojuredocs.

For whatever reason, this group has managed to avoid my early folly. In fact, there is a lot of emphasis on finding the right function. I'm learning a few new ones along the way.