Coda Hale { writing, projects, about, contact }

A Small, Nice Thing

In which I stop and smell the roses.

Lately, I’ve been programming in Clojure and enjoying it quite a bit. It’s my first Lisp, a return to dynamic typing, and in many ways the first truly functional language I’ve used in anger. One of the things I’ve been enjoying about Clojure is its regularity and the effects that has on composability.

Consider taking two sequences of numbers and adding their elements together in a pairwise fashion:

\[\left[\begin{array}{ccc}1 & 2 & 3\end{array}\right] + \left[\begin{array}{ccc}4 & 5 & 6\end{array}\right] = \left[\begin{array}{ccc}5 & 7 & 9\end{array}\right]\]

From a purely syntactic perspective, this is usually a simple task, but syntactic complexity is often misleading. When learning a new programming language, I often stop to consider how many concepts would need to be explained to a novice familiar with the underlying domain.

O, Complexity!

Scala

(a, b).zipped.map(_+_)

For Scala, you’d need to explain tuples, regular method notation, zipped, dotless/parenless/infix method notation (e.g. _ + _), placeholder syntax (i.e. _ and why it’s different than the _ characters elsewhere) and anonymous functions (which would probably include some talk about how they’re only contextually distinguishable from everything else).

(Or you’d need to explain Scalaz, semigroups, typeclasses, third-party dependencies and management tools, imports, implicit methods, infix method notation, &c. &c. There are a lot of ways to golf this in Scala, all of which require a large number of concepts to explain.)

Ruby

a.zip(b).map { |(x, y)| x + y }

Ruby would require an explanation of method invocation, zip, map, method blocks, and parameter destructuring, and infix notation.

Python

[sum(t) for t in zip(a, b)]

For Python you’d need to explain list comprehensions, function invocation, sum, and zip.

Javascript

a.map(function(x, i) {
    return x + b[i];
});

Javascript requires method invocation, map, anonymous function notation, the return keyword, infix operators, and array access.

Go

s := make([]int, len(a))
for i, x := range a {
    s[i] = x + b[i];
}

Go is unsurprisingly a bit more verbose, as you need to explain make, slice type notation, len, short variable declarations (i.e. :=), range loops, and index expressions and assignment,

Clojure

(map + a b)

For Clojure you need to explain s-expressions and map. That’s it. Literally. Clojure is a Lisp-1, so referring to the + function is as simple as using the + symbol.

O, More Complexity!

What happens if we need to add a third list of numbers, c?

Scala

(a, b, c).zipped.map(_+_+_)

Scala handles this relatively gracefully, requiring only a 3-tuple receiver and another infix operator in the anonymous function.

Python

[sum(t) for t in zip(a, b, c)]

Python just needs an additional parameter to zip. Because sum operates on iterables, and zip returns a list of tuples, both forms are almost identical.

Ruby

a.zip(b, c).map { |(x, y, z)| x + y + z }

Ruby needs an additional parameter to zip and another addition.

Javascript

a.map(function(x, i) {
    return x + b[i] + c[i];
});

Javascript adds another array access and addition, which isn’t too bad.

Go

s := make([]int, len(a))
for i, x := range a {
    s[i] = x + b[i] + c[i];
}

Go handles the situation similarly.

Clojure

(map + a b c)

Like Python, Clojure remains almost exactly the same as the original. We add c as an additional parameter to map—that’s it.

map is variadic, which avoids the tuple nesting seen in Ruby and Scala; like Python’s sum, + is neither infix (as Clojure has no such thing) nor binary. Like map, it’s a variadic function:

(+ 1 2 3) ;=> 6

If either map or + were of fixed arity, this would not be possible.

Unlike Python, though, Clojure’s example doesn’t require list comprehensions. (Python does have a map function, but it’s no longer considered Pythonic.)

tl;dr

The regularity of Clojure as a programming language provides many opportunities for composition compared with other programming languages, which in turn allows for concise expressions of common bits of code. I think that’s nice.

Updated January 30, 2016

  • Changed the Python example to use list comprehensions, which I’m assured are a more idiomatic way of doing things. Thanks, @michalmigurski!
  • Cleaned up a confusing sentence in the second paragraph. Thanks, @colby!
  • Included infix notation in the Ruby concepts.
  • Changed the Python example to use sum, which seems pretty nice. Thanks, @AnthonyBriggs!

Updated January 31, 2016

  • Changed the second Ruby example to use multiple arguments to zip. Thanks, @lindvall!