I started using clojure.spec’s fdef for speccing functions recently. The short version: it’s super fun and mind-blowingly useful.
Things I learned
- fdef + instrument are awesome. They are great for building up incrementally the program you’re trying to build. They are also great for finding bugs. Spec a function, get error messages if the function is called incorrectly, e.g. when trying things at the REPL or during tests.
- Admittedly, I had heard this before: function specs can specify predicates on a function’s arguments and its return value. This means you can make statements about the relationship between them. I.e. a function that takes a list and an integer index as an argument, and the index must be a valid index of the list (between 0 and length of the list). This also means that the error messages are way better, because they contain the predicate that is violated. (see also Stu Halloway’s screencast)
At this point spec already blows Java out of the water. Dependent type systems might be able to express some of the type relationships. But specs can express more than can be proved. - Specs are „à la carte“, meaning you can add them, but you don’t have to.
- You can spec functions in another namespace. This keeps the code clean. You can also throw away specs if you no longer need them.
- You can spec external libraries if they don’t have specs yet (!). Even if they had specs you could spec them with how you want to use them. Super awesome.
When searching for a bug, this also can come in handy. You can spec a function from a different namespace, call the code with data that reproduces the error and see whether the function is called correctly. This allows eliminating potential error cases. All without even touching the code. In addition to REPL aided development and debugging this is yet another tool to help you understand your program.
- Verbosity. In all honesty (it really hurts to say this), function specs are more verbose than Java’s function signatures. There are several aspects that counteract this:
- Specs usually go in their own namespace and thus cannot clutter your code
- You don’t need specs everywhere, only where it’s useful
- The compositionality are great (as usual in Clojure land) and you and up reusing specs a lot.
- Literally the first thing I did was to over-spec a function. Don’t do it. Make fspecs as general as possible. As Alex Miller puts it „specify what MUST be true about a function“ nothing more. Otherwise it will be less reusable and/or unable to grow.
When I specced the function I went into „define types like it was Java“ mode without thinking. I specified exactly what type the arguments were (two List of certain keywords). I found bugs in calls to that function and was happy. Later on I realized I could reuse that function and apply it to other sequence-like things. Now I had to go back to the function spec and change the expected type of the arguments to just „sequence?“, because that is what my function relied on really.
What’s interesting to me is how quickly and easily too specific argument types of a function prevents reuse. In Java those two uses of the function would probably not be reused and I would end up with redundant code. Or I would have to make a common abstraction for those things and have the function work on that. Either way it’s a lot of work and unnecessarily so.
Conclusion
To all fellow Java programmers/sufferers: if Clojure feels too dynamic at first, function specs might be the thing for you. They restore the immediate feedback of types. Only way better feedback and way more flexibly:
- Checks for correct calls. Error messages provide the erroneous values as well as the predicates that were violated
- Able to express arbitrary constraints
- „À la carte“-ness
On their own they might seem like small things, but together they change the way I program. Needless to say I’ll be using function specs a lot from now on.
I’ve really only touched on a few features of spec. There’s also conformance, generative testing as well as reuse and the compositionality of specs. Spec is a huge upgrade for Clojure.
No one has said it better than Arne Brasseur:
„Clojure Spec is one of the most exciting things to happen in Clojure land, and probably programming in general, in a while.“
Edit: I used this quote to convey that spec is amazing for Clojure. I did not mean to say that it is new. Spec is based on decade old research. Function specs are also nothing new. They’re called contracts in other languages, like Racket. It seems to me Racket even has generative testing based on contracts. Dear Racketeers, what you have been using for years is amazing. More languages should have this.