PurelyFunctional.tv Newsletter 452: Domain Invariants
Sign up for weekly Clojure tips, software design, and a Clojure coding challenge.
Design Idea 💡
This essay continues the series about Domain Modeling.
A domain model has three pieces:
Today, I'd like to talk about invariants and how we can encode them in a programming language.
Invariants are things that always hold in our model, no matter its current state. There are some simple invariants, like the first name of a person is always a string. And there are more sophisticated invariants like that a particular operation is idempotent. Invariants help us understand the properties and relationships of and between operations and information.
We want to encode our domain invariants in code if possible. Different languages give us different ways to do that. Here are some places to encode invariants:
- Language features
- Data structures
- Runtime checks
- External proofs
- Your brain (not code!)
Your language might give you a type system that lets you easily enforce that a first name is always a string. Some type systems are more expressive and might let you encode sophisticated invariants.
Tests are a common way to encode invariants. Property-based tests can encode algebraic properties such as idempotence and associativity.
Many languages have features that maintain invariants. For instance,
Clojure's data is immutable by default, so you can be sure nothing gets
synchronized keyword ensures that a method can not
be called more than once at the same time. That may be useful for
Data structures often have invariants. For example, sets do not contain duplicates. Adding to a set is idempotent. You can use that property to implement idempotence.
It is common to add assertions near the beginning of a function to check that the arguments maintain essential invariants. These can communicate to the reader of the code and help find bugs during development.
You can write down the invariants in documentation to help readers understand the domain model.
You can ensure that invariants hold by writing a proof, optionally using a proof assistant. A common language for writing these is TLA+.
You can also hold your invariants in your head. It is a widespread practice. The biggest downside is that your invariants don't work when you're not there. Also, our brains are very limited and often overlook corner cases.
Each of these places has costs and benefits, and not all are possible for every language.
Upcoming presentation 📢
December 4th I am speaking at re:clojure 2021 about Domain Modeling.
It's all online so see if you can attend, too! It's all free!
Book update 📘
I wrote Grokking Simplicity because I couldn't find a book for beginner functional programmers. People kept asking what book I recommended and I had no answer. Through countless discussions about what functional programming is and what its value is, I refined the idea of actions, calculations, and data. That idea became the core of the book. I've received lots of messages from people saying that idea was instrumental in their understanding of functional programming. It's very rewarding.
Apropos Episode 📽️
Pandemic update 😷
I know a lot of people are going through tougher times than I am. If you, for any reason, can't afford my courses, and you think the courses will help you, please hit reply and I will set you up. It's a small gesture I can make, but it might help.
I don't want to shame you or anybody that we should be using this time to work on our skills. The number one priority is your health and safety. I know I haven't been able to work very much, let alone learn some new skill. But if learning Clojure is important to you, and you can't afford it, just hit reply and I'll set you up. Keeping busy can keep us sane.
Stay healthy. Wash your hands. Wear a mask. Get vaccinated if you can. Take care of loved ones.
Clojure Challenge 🤔
Last issue's challenge
This week's challenge
Write a function that determines whether a sequence of integers can be
rearranged into a sequence of consecutive numbers without duplicates.
The function should return the sequence of consecutive numbers or
if it is not possible.
(consec ) ;=> () ;; trivially true (consec ) ;=> (1) ;; ditto (consec [3 1 2]) ;=> (1 2 3) (consec [5 3 2 1]) ;=> nil ;; non-consecutive (4 is missing) (consec [7 8 9 7]) ;=> nil ;; 7 repeats
Thanks to this site for the problem idea, where it is rated Hard in Java. The problem has been modified.
Please submit your solutions as comments on this gist.