Bill Adams bio photo

Bill Adams

Bill is an engineer and former muscian. He likes all things SOLID, TDD'd and Functional with a light dusting of the Applicative.

Email

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:

engineer@conversant:~$ scala

The REPL should boot-up and present you with a greeting:

Welcome to Scala version 2.11.4 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_31).

Type in expressions to have them evaluated.

Type :help for more information.
 
scala> 

Customer-Contact sports

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.

scala>  val customers = List()

customers: List[Nothing] = List()

Ok, Ok bad pun. What kind of List do we want? Customers!

scala> val customers = List[Customer]()

<console>:7: error: not found: type Customer
       val customers = List[Customer]()

Sticklebats! We need a Customer.

What is a Customer? For now just a data-structure. Let’s create a Customer so we can get something to compile.

scala> class Customer

defined class Customer

scala>  val customers = List[Customer]()

customers: List[Customer] = List()

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.

scala> class Customer(val enabled: Boolean = false)

defined class Customer

Great! Now let’s use filter to extract only the enabled Customers.

scala> val customers = List(new Customer, new Customer(true)).
                          filter( c => c.enabled)

customers: List[Customer] = List(Customer@1f2f9244)

There’s our 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 case statement:

scala> case class Customer(val enabled : Boolean = false)

defined class Customer

scala> val customers = List(Customer(), Customer(true)).
                         filter( c=> c.enabled)

customers: List[Customer] = List(Customer(true))

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 Customer.

scala> case class Customer(val enabled : Boolean = false, 
                           val contacts : List[Customer] = Nil )
defined class Customer

scala> val customers =  List(Customer(), Customer(true)).
                          filter( c=> c.enabled)

customers: List[Customer] = List(Customer(true,List()))

Lets move on to fulfilling criteria #2 - “…and has contacts”. We’ll just ‘pile on’ to the existing expression…

scala> val customers = List(Customer(),
                            Customer(true),
                            Customer(true, List(Customer()))).
                               filter( c=> c.enabled).
                               filter( c => c.contacts.nonEmpty)
                               
customers: List[Customer] = List(Customer(true,List(Customer(false,List()))))

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.

Make our List of customers a value.

scala> val customers = 
    List( Customer(), 
          Customer(true), 
          Customer(true, List(Customer())))

customers: List[Customer] = 
    List( Customer(false,List()), 
          Customer(true, List()), 
          Customer(true, List(Customer(false))))

scala> customers.filter( c=> c.enabled).filter( c => c.contacts.nonEmpty)

List[Customer] = List(Customer(true,List(Customer(false,List()))))

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 name field.

scala> case class Customer(val name : String,
                           val enabled : Boolean = false, 
                           val contacts : List[Customer] = Nil )
defined class Customer

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.

val customers =
     List( Customer("TC-1 NOT ENABLED") , 
           Customer("TC-2 NO CONTACTS", true), 
           Customer("TC-3 NO DISABLED CONTACTS",
                                 true,
                                 List(Customer("TC-3", true))), 
           Customer("TC-4 PASS", true,
                                 List(Customer("TC-4 PASS CONTACT"), 
                                 Customer("TC-4 Doesn't Matter",true))))

scala> customers.filter( c=> c.enabled).filter( c => c.contacts.nonEmpty)

List[Customer] = 
    List(Customer(TC-3 NO DISABLED CONTACTS,
                  true,
                  List(Customer(TC-3,true,List()))),

// the rest is elided for brevity

Now that we have our data lined up we are ready to tackle Criteria #3 -

“At least one of a customers’ contacts is disabled.”

The nested 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 map function.

scala> customers.filter( c=>c.enabled).
                 filter(c => c.contacts.nonEmpty).
                 filter( c => c.contacts.
                     exists( contact => !contact.enabled )).
                 map ( c=> c.name )

List[String] = List(TC-4 PASS)

Excellent! Now all we need is the count - that’s pretty straightforward.

scala> customers.filter( c=>c.enabled).
                          filter(c => c.contacts.nonEmpty).
                          filter(c => c.contacts.exists (
                                   contact => !contact.enabled
                       )).size
Int = 1

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 :

scala> customers.count( c => 
                 c.enabled && 
                   c.contacts.exists(contact => !contact.enabled))

res2: Int = 1

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.