Lessons learned from solving 4Clojure problems

A few days ago I completed the last problem on the 4Clojure site. If you want to learn Clojure, solving these problems is a great way to do it. Several of its features -that I'll highlight below- make it a great learning tool. Other features probably arise from Clojure being a (pragmatic) functional programming language. Coming from mostly an OO background these were also new to me and thus deserve their own paragraph.

I hope that after reading through the list you'll end up being persuaded of the merits and want to solve (some of) the problems yourself. If you do, please let me know how it went and what you learned from it.

If you really get stuck, there is a Google group dedicated to the 4Clojure problems. You can also leave a comment here so I can help or go directly to check my solutions. Let's jump in.

Why is 4Clojure a great learning tool?

Looking at others' solutions

After solving a problem, you can check how the users you follow solved it. That's arguably the most important feature when it comes to learning since it is essentially a code reading exercise when the functionality of the code is well-known (since it solves the same problem you've solved) and the authors are probably more proficient.

On several occasions I saw solutions that were both more concise and clearer than mine (especially when tackling a hard problem). Dealing with the inferiority complex on the very short term is dwarved by how much wisdom you gain from these. For what it's worth, the users I learned most from are hypirion, jafingerhut and chouser.

(If you'd like to follow me, I'm balint there.)

Executable, well thought-out test cases

To submit your solution, you paste your code into a textbox and click a button. The test cases, which are visible, are then checked one by one. If all the lamps become green, your solution is accepted. If not, you get an error message and have to try again.

This method has several advantages. First of all, it eliminates the imprecisions you might have had after reading the description of the problem. Second, it gives you a set of examples to work against. Third, they force you to think deeper about the problem since they are constructed to reject a partial solution.

Timeouts

Your solution can be functional and pass all the test cases but if it does not finish in a certain time, it will get rejected. I bumped into this on several occasions. Most of the time it was because I came up with the brute force solution to a hard problem and hoped I'd get away with it. In other cases it was because of a technical issue, like allocating too much memory.

In the first case, it made me think again about the problem (see Hammock-Driven Development) and come up with a more ingenious solution. In the second case, I learned something about a technical aspect of the language. In both cases, I grew a bit wiser about optimization -which is a "real-world" coding skill- so I'm happy the authors of 4clojure chose to implement this constraint.

What does one learn about (functional) programming?

Hammock-Driven Development

Also known as “step-away from the computer to solve hard problems”, Hammock-Driven Development is a term coined by Rich Hickey, the creator of Clojure, in a keynote speech.

Apparently ridiculing Test Driven Development (TDD), HDD holds that the most important activity to solve a hard problem is to think deeply about it without any distractions. Most of the time sitting in front of the computer is a distraction in itself since it begs to be typed on and prevents actual deep thinking to happen.

This one is really hard to get used to because whenever we write code we feel like we're getting closer to a solution. Thinking, on the other hand, does not provide any tangible output.

However, HDD has rung ever more true with me as I progressed. When tackling hard problems, I tended to think about them for some time and then started to type in actual code. The problem was, when I felt that the solution became convoluted I did not go back to the proverbial hammock but carried on with the implementation. Most of the time it either turned out to be a dead-end or a solution I'd much better hide.

Even more importantly, a cleaner solution is one that is easier for others to understand. Since code is mainly for others to read and occasionally for computers to execute, more thinking up-front results in less time spent developing and maintaining the code down the line.

The REPL is a powerful developer tool

The power of the REPL is one those realizations most of us coming from OO will come to. Since FP languages have very little state and side-effects and thus a lot of idempotency, trying things at the REPL is taken to the next level. You launch a REPL once and then copy-paste the building blocks of your solution between your editor and the REPL (and there are better solutions then copy-pasting).

Use higher level functions

FP languages strive to have a small set of data structures and a high number of functions that operate on them. Clojure is no exception. Though it's not hard to assemble the higher-level function you need yourself, in the majority of cases, it's just extra work: it's probably already defined in the core.

This is something that I learned by reading others' solutions and learning about awesome functions (frequencies, merge-with, condp come to mind). After a while I looked up the high-level function myself from the cheatsheet.