Compile time spec checking with Clojure
Many things have been said on typed vs untyped languages, so I’m not going to repeat them here. Clojure is an inherently a dynamic language, yet to have some sort of safety especially at the system’s boundaries it’s answer was the introduction of the clojure.spec library.
Spec allows one to specify the structure of data (and functions), parse and validate the data as well as generate test data from a given specification. Spec in that sense serves as documentation of your program, can give better error messages and better error handling in general, facilitate testing and increase the extensibility of programs.
There are same similarities and differences between spec and types, and I tried to sum them up here:
- types provide semantic information from the compiler
- types enforce strictness
- types force the system design
- types provide stronger guarantees (even spec generators can’t guarantee a whole spectrum of possible values)
- types are checked at compile time
- spec is optional: when and how to verify inputs is up to you
- specs favour being expressive: expressivity > proofs
- specs are less efficient performance wise
- specs are checked at runtime
In this post I wanted to focus on the last point - even though spec are checked at the program runtime, clojure macros are availiable at compile-time, and we can use this fact to achieve something like a type safety in clojure, should you ever need it. Some people are apparently worried about types
Compile time checks
In this blog post we will spec and define a divide function which, well… divides two integers. Then we will try to make it type safe.
Spec has a special function
clojure.spec/fdef for defining the input and output specifications of functions. Function can be fully specified with three specs:
- One for it’s arguments.
- One for the return value.
- One (optional) for the relationship between the inputs and output.
Let’s go ahead and write a function spec for the divide function. We want the input to be integers, but not 0, the output a floating precision number, and the relationship between them is multiplication.
Once we have the spec we can write the function itself:
To turn on validation of the arguments, i.e. runtime checks that the function is being called correctly we call
So far so good. But what if somewhere in the code we have this lurking:
we won’t know of the problem right until the function
divide-by-foo is called. Solution? Wrap the function inside a macro (macros are always checked during macro expansion time):
Now try to compile the function below in your REPL session:
You should see a nice spec’d error message like the one below:
You can find the code for this post found here https://t.co/D4EqC5pSTb. Thank you for reading!