As I was preparing for my presentation on Clojure data structures I flashed back to a recent programming task I had to overcome. I don’t remember exactly what it was but it involved grouping data by particular keys and having an array of values for each key. When such a task rears its head I always have to play around in the REPL (err, the browser console) to see the exact working of each function I am about to rely on.
1 2 3 4
I spend a considerable amount of time trying these out and since I discover no overarching design behind how they operate I come away none-the-wiser and so repeat the same process on a slighly different problem some time later. (slice vs. splice, anyone?)
The point I want to get across is that not having a solid design principle behind a language is actually a hindrance. It slows you down by having to try the pieces (functions, in this case) individually unless you have memorized them.
On the other hand, when you work with a language that has a design philosophy you gain the benefit of faster development. In Clojure, in the case of collection types, that principle is immutability. You don’t have to read the docs or fiddle around in the REPL. The original never changes, you get the “modified” version in the returned value:
Let’s look at another language, Ruby.
Called on an array,
unshift all modify
unshift return the modified array, while
pop return the removed element. So the rule seems to be that
methods that expand the array return the new array, methods that take away an
element return that element. So far so good.
drop? They both take away a number of elements, from the
beginning or from the end, and the number of elements being passed in as a
parameter. I’d totally expect these methods to change the original in place.
After all, few names sound more destructive than
drop. We’re in for a
surprise. Both of them leave the original intact and return the taken (or
dropped elements). In fact, you can pass in a number to both
which makes them identical to
drop with the important distinction
of whether they mutate or not the array they are called on.
To add to the confusion, there is
delete which deletes all occurrences of the
element that is given as the parameter and there is
compact which does exactly
the same for a special case, nil. (So
compact takes no parameter). However,
it turns out that
delete is mutating, but
compact is not, preferring to return
the array with no nils.
compact! comes to the rescue which does mutation and so
x.compact! is the same
Learning the same thing in Clojure is like learning articles in English: there is one true way to do it.