As professional developers we apply many disciplines to help ourselves deliver code that is reasonably well-designed and bug-free. I have been an adherent and practitioner of Test Driven Development (TDD) for many years and it is a discipline that has served me well. However when using languages that take a functional approach : such as Scala or Clojure - I have had to re-evaluate the way I apply certain disciplines like TDD.
The REPL - getting things done … fast.
Scala and Clojure are almost polar opposites when it comes to complexity, syntax, typing and style but they embrace many of the things that make functional programming a joy : an emphasis on values, referential transparency, persistent data-structures and other goodies like being able to form processing pipelines that yield computations and/or transformations of data.
These languages also have something else in common with pretty much all of the ‘functional languages’ out there : The Read Eval Print Loop or REPL
A REPL is a rather simple but indispensable tool that helps us experiment and get quick feedback on ideas we want to explore and test without a cumbersome IDE. With the REPL we can break down a problem into steps and quickly and iteratively build up those steps into a solution. The rest of the article is going to explore the idea of using the REPL to drive out the details of a given problem.
A little setup before we launch into the deep
If you have java installed and would like to try the following examples your self please download and install the Scala REPL.
If you prefer pretty printing and syntax highlighting you can try Ammonite.
If you have Scala installed just fire-up the REPL at the command line like so:
The REPL should boot-up and present you with a greeting:
Here’s a somewhat contrived problem we can iterate over with the Scala REPL.
“We are in Sales and we want to get a count of ‘enabled’ Customers who have at least one ‘disabled’ contact.”
Decomposing this sentence we might say that:
We want a count of Customers who : 1. Are enabled, 2. Who have contacts, 3. Of those contacts at least one of which is disabled.
For our purposes we don’t have a
Customer handy - maybe its in another artifact, package or library.
Its just a data-structure.
For this exercise our ‘design’ of the data will be exploratory. We’ll augment a
Customer in an ad hoc fashion to get to our goal. It’s often helpful to have the desired inputs and output data-structures predefined. But I think we can get away with a little exploratory coding for now.
A problem well stated is a problem half-solved.
Reviewing our list above - it says we need to end up with a count of something but we have nothing. So maybe counting should be the last thing on our minds.
Let’s re-order and rewrite the Criteria a bit:
From a list of Customers we want: 1. Those who are enabled, 2. Have contacts, 3. Of those contacts at least one is disabled. 4. Get a count of the above.
This list won’t win any writing awards but it organizes our thoughts nicely and that’s probably the most important thing in any case.
RED: Failing as fast as we can.
We need to get to Nothing quickly so let’s do that now.
Ok, Ok bad pun. What kind of List do we want? Customers!
Sticklebats! We need a
What is a
Customer? For now just a data-structure. Let’s create a
Customer so we can get something to compile.
And there was great rejoicing.
GREEN: and staying GREEN. Knocking out our Criteria
The first criteria is to be able to find
Customers who are
enabled. Let’s amend our
Customer class with an
enabled member value that defaults to false.
Great! Now let’s use filter to extract only the enabled
Customer! …but its a bit unsatisfying and its going to get hard to distinguish Customers pretty quickly. Let’s enhance our
Customer to have some useful to-string, equality and factory functions with the
That’s better! Onto criteria #2: find
Customers with contacts. What are ‘contacts’? Peeking ahead to Criteria #3 - it says something about Contacts being
enabled so maybe we’ll just reuse Customers as our Contacts? Customers have some notion of
enabled-ness so it seems plausible we could just reuse
Lets move on to fulfilling criteria #2 - “…and has contacts”. We’ll just ‘pile on’ to the existing expression…
So far so good - scratch Criteria #2 .
However I’m sensing that our command line is getting a bit ‘crowded’ and when things get crowded its easy to make mistakes.
customers a value.
The output is ‘nice’ but again its getting a bit hard to distinguish
Customers. We’ll ‘tag’
customers with a required field (no default value this time) to distinguish test cases by adding a
If we don’t wind up using
name for anything useful later on we can delete it. For now we’ll update our test-cases to help us keep track of our results.
Now that we have our data lined up we are ready to tackle Criteria #3 -
“At least one of a customers’ contacts is disabled.”
Customer-contact data isn’t particularly interesting to me as output at this point. I want to know just the bare-bones about
customers that meet my criteria. Let’s clear up some of the noise in the output by projecting only the
customers' name: using the
Excellent! Now all we need is the count - that’s pretty straightforward.
And we’re done.
REFACTOR- Reflect and Revisit.
Was this the “best” most concise, most idiomatic implementation ever? No not really. We just built something up iteratively mapping our requirements to code. But now that we’ve driven out what the shape of and size of the data we are free to try another approach. Maybe something like this :
I think its also noteworthy that this solution reads like our original description:
“We want to get a count of enabled Customers who has at least one disabled contact.”
To TDD or not TDD? Using the right tool for the job.
Languages supporting the functional paradigm like Scala coupled together with tools like a REPL allow programmers to express and test ideas experimentally and with confidence. With TDD we generally don’t write tests for “can’t fail code” (i.e. getters, setters, constructors, etc.). If pure computation is the ‘question’ then using a tool that’s best at testing interactions is not really the best ‘answer’.
Whereas testing Object-Oriented code tends to be about checking ‘interactions’, Functional Programming focuses on computation and transformation of data.
Therefore I’ve found TDD to be less beneficial when crafting parts of a system which ‘just’ do either pure computation or transformation of data. The idea is to use the right tool for the job. Functional languages coupled with REPL-Driven Development presents an exciting alternative to enhance but not necessarily replace a Test-Driven approach.