Clojure's Quiet Achiever

The sequence abstraction is at the heart of much of Clojure's power, but receives little attention. I want to tell you what's so great about it!

Iteration and recursion are considered fundamental programming concepts and show up early in any 101 course no matter what the paradigm. The sequence abstraction almost completely throws these tools out the window. You don't need them. That's the big point I want to make here; this huge fundamental part of programming is unnecessary once you have a good sequence abstraction. I can't oversell how much complexity that removes. There must be a catch...

By my count there are 80 functions in Clojure core for manipulating sequences in some way. When you first are exposed to them the natural reaction is a feeling that the cure is worse than the disease, that something simple like a loop is now a mammoth set of functions you need to learn and understand. Loops are just one thing, surely sequences are more complex. However consider for a moment that loops do lots of different things. You have to understand every loop you come across from start to end. Sequence functions abstract this away.

A well worn example is (reduce * (range 2 10)) computes the factorial of 10. We have a sequence of number that need to be multiplied together, so we can express it like that instead of an iterative expression. Lots of languages support the heavy lifters map and reduce, so what's so special about Clojure? My premise is that Clojure has a fuller set of supporting functions which mean you almost never have to drop down to loop/recur. It also has lazy sequences and persistent data structures which mean it will be fast, and you don't need to worry about the implementation.

Another simple example... Say we have a vector of numbers
(def v [1 2 3 4 5])
And we wanted to perform some operation on the sum of each item and the next item. We might implement this imperatively like so:
for i=0; i<v.length-1; i++
operate( v[i] + v[i+1] );

So how would this look like in sequence operations?
(map operate (map + v (rest v)))

Lets break that down,
(rest v) the sequence of v without it's head: (2 3 4 5)
we are mapping the + operator to two sequences: (1 2 3 4 5) and (2 3 4 5)
which will result in a new sequence (3 5 7 9)
which we map the operation over.

You can read the statement as
"create a sequence of results of calling operate on a sequence made from adding items from a sequence of v to a sequence of v without it's head."
user=> (map inc (map + v (rest v)))
(4 6 8 10)

The parts can be individually understood. They can be combined to perform pretty much anything you need to do. As individual pieces they are simple enough to understand and make building blocks to describe all sorts of transformations. This is true of all means of abstraction and means of combination, and especially true of functional abstractions. The sequence abstraction can be extended also. Check out this interesting library which allows you to do SQL like queries on sequences.

Clojure really exploits the sequence abstraction well to remove a major source of mutation and state: iteration/recursion. Sequences are built into the language and well supported with many useful utility functions.

Live long Java

One thing I really love about Java is the quality of its libraries. The famous Java Ecosystem. For just about any problem domain you'll be able to pickup something that works great and is well supported. Most of the time you wont even need to leave the massive standard library. They really do have something for everyone. And usually there is a pretty clear winner so you don't have to spend your time trialling solutions or wondering how to integrate them.

Lets look at client/server communications for example. Sockets socket sockets, so much fun to be had! So many choices to be made. Will you use threads? Will you support TSL? What wire format to choose? Are you after performance, or simplicity? Yes you can tackle all those problems with due vigor, but someone already has! Netty gives you a completely configurable framework so you can treat all those decisions as options and just focus on your client/server protocol. This is no small thing, decoupling your messaging from the transport layer. For example UDP and TCP are totally different abstractions and it is a non-trivial task to come up with a solution which allows you to plug in either and still work.

Java really has reached a very strong level of 'this is how you solve that problem'. And for that, I hope it stays around for a long time.

Believe the computer

If you support software you are probably familiar with this scenario:

A person responsible for system X seeks your help because "System Y is broken because it is reporting problem Z with X". The general premise is that it must be System Y's fault, because X is not reporting any error. You respond "X has problem Z". But they can't find any problem Z, it must be Y. Well it is true, Y is not infallible, it could be reporting incorrectly or not doing the right thing. You check the logs, follow the interactions, check any values/times/events... and come to the conclusion "Y indicates X has problem Z". After explaining your findings, they fix Z in X. I've been on both sides of the equation.

Why do we find it so hard to imagine an error occurring on 'our' end? Well if we could imagine the error, we would already be on the path to fix it. Also sometimes checking the 'other end' quickly reveals something outside your control. The second reason is however a big red warning flag. If 'this end' does not provide enough information to tell you whether it is successfully interacting with some other system, it is time to add that feature. This is the first step to being able to diagnose this problem and avoid getting caught in the same cycle for the next one. Once you have that information, you are one step closer to imagining the real cause.

This topic is closely related to the phenomenon whereby when stuck with a seemingly unsolvable problem, simply telling someone else about it can point out an answer without any input from the other person. Some workplaces find this so compelling they set up a doll or teddy bear that developers have to speak to before they ask any one else. The great thing about computer systems is they can be vastly more informative than teddy bears. If you believe them!