PurelyFunctional.tv Newsletter 374: Seek truth, not convenience
Clojure Tip 💡
Seek truth, not convenience
I got a great question from a reader: "How do you get better at making abstractions?"
Just a moment of humility: I have a particular viewpoint here that I've arrived at. It works for me. I hope it can work for you, too. More important than finding the one right viewpoint is having a variety of viewpoints to choose from. So I hope this viewpoint expands your ideas and helps you have a richer experience of the world.
Okay, now that that's done, I'm going to go straight ahead.
People often talk about software design as a way to write code that adapts to changing requirements. They propose design patterns and best practices for making your code convenient to change. I've always found this definition of software design slightly wrong. To me, software design, and in particular the building of abstractions, is a process of seeking truth. You seek a representation in code that faithfully represents the important concepts in your domain. Prioritizing the ability to change is doing it backwards. And, ironically, seeking truth is the best way to ensure that your code is easy to change when requirements are in flux.
Being able to change code is a very important concern. But the best way to do that is by finding some layer of ground truth that is unlikely to change. Pull things apart into separate layers. Keep pulling until you find a layer so fundamental that it obviously won't need to change. That fundamental level will, of course, be more general than the problem you are trying to solve. Then you can build specific solutions out of it.
An example I gave in this podcast episode was from a previous job I worked at. The problem was to determine who was eligible for which elections. But in the US, there are 50 states, each with their own rules. Some states required you to register ahead of time. Some let you show up the day of. Some required identification. Some required you to live in the state for several months. If you laid it all out, it looks like a giant mess. And that's typical of looking at any domain. They always look like an impossible ball of yarn to unravel.
But that just means you're looking at the wrong level of abstraction. You need to get more general. If there doesn't seem to be any order at the level you're looking at, dig deeper. Pull the problem apart. That's what we do as Clojure programmers. So if you did that, you might try several ways to pull it apart, like you're pulling on threads to see which unravels your shirt. If you keep trying threads, you find some that do get you closer. You're seeking truth.
One technique is to ask yourself what the function signature of this
function you don't know how to write is. What does it mean to decide if
someone can vote in an election? Well, you might find this simple
(can-vote-in? person election) => true/false. That's a
really good guess and should come with some feeling of discovering
something profound. Yes, this has the ring of truth. That's what you
should be seeking.
Now, you could take that further.
true/false is not a very rich set of
choices. You might want to include a reason why they can't vote. Or
maybe it's not completely decideable. Maybe you need
maybe-if would let you say something
like "You may be able to vote if you have a valid driver's license".
Anyway, we don't need to solve this problem. But we did find an important waypoint on the way. Remember: you're looking for something that is unlikely to change. What could cause this signature to change? Changes to laws and changes to feature requirements. Ask yourself whether you can imagine a change that would invalidate your decisions. How likely is it? Develop that intuition.
Now you have your signature. Your rules can conform to it. For instance,
(is-resident? person election) => yes/no/maybe or
(is-voting-age? person election) => yes/no/maybe. You can develop a
way to compose these rules, probably something like Boolean algebra. Is
that means of composition likely to change? How likely is Boolean
algebra to be the wrong thing?
Now, of course, the laws will change. And features will change. But that's just some froth on the top of a deep, calm ocean of code. The messiness of the laws is still there. You didn't eliminate it. You just made a good way to express the laws---by finding something that doesn't change. That's why seeking truth is better than seeking convenience. If you seek convenience, you add a bunch of indirection in the hopes that it makes things easier to change later. But when you seek truth, most of your code doesn't change. The stuff that does is sitting at the top, with very little other code relying on it. Your functions naturally stratify into layers of rate of change.
Okay, that's enough pontification. I go deeper into this in Chapters 8 & 9 of Grokking Simplicity. In those chapters, we talk about stratified design, and they should be coming out soon. But a lot of the modeling part I have to save until Part 3 (Chapters 17-21).
Quarantine 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. Stay at home. Wear a mask. Take care of loved ones.
Clojure Media 🍿
Book update 📖
I'm currently writing Chapter 10. It's slow because my kids are home and I have to steal a few hours per week to work on it. It's all about first-class functions. Chapters 1-7 are published. 8 & 9 are in the can, ready for the publisher. I'm planning 22 chapters total.
You can buy the book and use the coupon code TSSIMPLICITY for 50% off.
Clojure Challenge 🤔
Last week's challenge
You can leave comments on these submissions in the gist itself. Please leave comments! You can also hit the Subscribe button to keep abreast of the comments. We're all here to learn.
Just a note on these past few weeks of problems. I've been using a site to come up with challenge ideas. Invariably, I have to look to the Very Hard problems in order to find anything that is not absolutely trivial in Clojure. If you've been solving these problems, congratulations! These are very challenging in other languages.
This week's challenge
Poker hand ranking
Write a function that tells you the best scoring for a given poker hand of 5 cards. For instance:
(score [[3 :diamonds] [3 :hearts] [3 :spades] [5 :hearts] [:king :clubs]])
=> :three-of-a-kind ;; three 3s
Cards are represented as a tuple of rank (number or name if it's a face
card) and suit. Face card names are the keywords
:jack. Suits are
Here are the hands, in order of importance:
Royal Flush: Ace, King, Queen, Jack, and 10 of the same suit
Straight Flush: Five consecutive cards of the same suit
Four of a kind: Four cards of the same rank
Full house: Three of a kind and a pair
Flush: Any five cards of the same suit
Straight: Five consecutive cards, not in same suit
Three of a kind: Three cards of the same rank
Two pair: Two different pairs
Pair: Two cards of the same rank
High card: No other hand available
Thanks to this site for the challenge idea where this is considered Expert level.
You can also find these same instructions here. I might update them to correct errors and clarify the descriptions. That's also where submissions will be posted.
As usual, please reply to this email and let me know what you tried. I'll collect them up and share them in the next issue. If you don't want me to share your submission, let me know.