Sunday 19 October 2014

Types don't substitute for tests

When reading discussions about the benefits of types in software construction, I've come across the following claim:
When I use types, I don't need as many unit tests.
This statement is not consistent with my understanding of either types or test-driven design. When I've inquired into reasoning behind the claim, it often boils down to the following:
Types provide assurance over all possible arguments (universal quantification). Unit tests provide assurance only for specific examples (existential quantification). Therefore, when I have a good type system I don't need to rely on unit tests.
This argument does not hold in my experience, because I use types and unit tests to establish different kinds of properties about a program.

Types prove that functions within a program will terminate successfully for all possible inputs (I'm ignoring questions of totality for the sake of simplifying the discussion).

Unit tests demonstrate that functions yield the correct result for a set of curated inputs. The practice of test-driven design aims to provide confidence that the inputs are representative of the function's behaviour through the discipline of expanding a function's definition only in response to an example that doesn't yet hold.

All of the examples that I use in my practice of test-driven design are well-typed, whether or not I use a type system. I do not write unit tests that exercise the behaviour of the system in the presence of badly-typed input, because in an untyped programming language it would be a futile exercise and in a typed programming language such tests would be impossible to write.

If I write a program using a type system, I still require just as many positive examples to drive my design and establish that the generalisations I've created are faithful to the examples that drove them. Simply put, I can't think of a unit test that I would write in the absence of a type system that I would not have to write in the presence of one.

I don't use a type system to prove that my functions return the output I intend for all possible inputs. I use a type system to prove that there does not exist an input, such that my functions will not successfully terminate (again, sidestepping the issue of non-total functions). In other words, a type checker proves the absence of certain undesirable behaviours, but it does not prove the presence of the specific desirable behaviours that I require.

Type systems are becoming more sophisticated and are capable of proving increasingly interesting properties about programs. In particular, dependently typed programming languages like Idris can be used to establish that lists are always non-empty or the parity of addition.

But unless the type system proves that there is exactly one inhabitant of a particular type, I still require a positive example to check that I've implemented the right well-typed solution. And even if the type provably has only one inhabitant, I would still likely write a unit test to help explain to myself how the abstract property enforced by the type system manifests itself.

A type system is complementary to unit tests produced by test-driven design. The presence of a type system provides additional confidence as to the correctness of a program, but as I write software it does not reduce the need for examples in the form of unit tests.

Sunday 12 October 2014

Hiding the REPL

I depend heavily on Clojure's REPL, because it's where I write music. Over time, however, I've become less focused on directly interacting with the REPL, and pushed it more and more into the background.

I use Vim Fireplace, which gives me the ability to evaluate forms in a buffer by sending them to an nREPL instance. There's also a REPL available within Fireplace, but I find I only use it for simple commands like stopping a piece of music or printing a data structure.

Speaking to Aidy Lewis on Twitter today, I've come to realise that there may be two different models for REPL-driven development.

Aidy described a model where the REPL is ever-present in a split-window. This brings the REPL to the foreground, and makes it conveniently available for experimentation. I would describe this as a side-by-side model.

On the other hand, I treat the buffer itself as my REPL. I write and refine forms, evaluating them as I go. If I want to experiment, I do so by writing code in my buffer and either evolving it or discarding it. My navigation and interaction are as they would be in any other Vim session, punctuated by occasional re-evaluation of something I've changed. This seems to me more like a layered model, with my buffer on the surface and the REPL below.

The reason I value this mode of interaction is it makes me feel more like I'm directly interacting with my code. When I make a change and re-evaluate the form, I have the sense that I'm somehow touching the code. I don't have a mental separation between my code-as-text and the state of my REPL session. Rather they're two ways of perceiving the same thing.