Clojure Tip 💡
where to find timeless truths
In the last issue, we discovered a timeless function signature that tells us whether a given person can vote in a particular election.
(defn can-vote? [person election]) ;;=> returns true or false
This signature is timeless. Despite all of the volatility in the domain, with changing laws, people moving, new elections happening, this signature sufficiently abstracts away those differences.
Timelessness is a good thing. It means other modules can rely on this signature forever. And it also means we can build on top of this.
The signature also leaves a lot unstated. What data is in
election? We don’t know yet. To consider the
can-vote? signature timeless, we must assume that
election contain all the relevant data for all time. That seems problematic since we can’t know what data future laws might require. How can we deal with that?
Luckily, we can always add new fields to
election as we need them. The name
person refers to the current collection of data associated with a person. As we add more fields to
person, the name will refer to that. Indirection through a name is a type of modularity. Name indirection isolates the signature from volatility in the collection of data.
We can basically push the question of what
election contain until later. If we do that, we have a very solid foundation on which to build further abstractions. We can use those abstractions to express the highly volatile domain.
Because we are returning a Boolean
false, we may look to Boolean algebra for some help. For instance, to vote in a Texas election, you need to be a resident of Texas and be at least 18 years old on the day of the election (among many other rules). Let’s assume we have those three functions:
(defn texas-election? [_person election]) ;=> true/false (defn texas-resident? [person _election]) ;=> true/false (defn major-age? [person election]) ;=> true/false
We can note that this requires a few pieces of data:
personcontains birth date
personcontains state of residency
electioncontains election date
But assuming these data exist, the implementations are quite obvious. Notice that these three “rules” have the same signature as our original
can-vote? signature. It is worth exploring whether we can build
can-vote? out of smaller pieces that are easier to write, read, and change, each of which have the same signature called rule. We can then decompose the problem into atomic rules.
We already have the clue of Boolean algebra. Let’s define a rule combinator that requires that two rules return
true, which corresponds to the Boolean AND operator.
(defn rule-and [rule1 rule2] (fn [person election] (and (rule1 person election) (rule2 person election))))
We note that we can also write a version that takes many rules instead of 2, but for space I’ll leave it there.
Now we can get creative.
(def texas-rules (rule-and texas-resident? major-age? ...)) (def texas-case (rule-and texas-election? texas-rules))
Remember, those aren’t the only rules in Texas. This is just an example to let us imagine what a complete “Texas rule” might look like. In fact, we can imagine we express all the states, like so:
(def new-york-case (rule-and new-york-election? new-york-rules)) (def oregon-case (rule-and oregon-election? oregon-rules)) ...
Then we could combine them all with
rule-or (whose implementation is left as an exercise for you).
(def can-vote? (rule-or texas-case new-york-case oregon-case ...))
Now, if Texas adds a new rule, we can just add it to
texas-rules. Nothing else has to change. We’ve isolated the volatility.
So, let’s discuss. We’ve found a timeless signature by looking below the morass of volatile laws. The timeless signature gives us a nice modularity to shield us from the volatility in the domain. But we can also use the signature to build up a better system for expressing the domain, in this case in the form of Boolean combinators.
We found the
rule signature by looking at a more general layer of abstraction. The
rule signature is able to express any yes-or-no question about a person and an election, not just about ability to vote. That generality gives us the flexibility we need to decompose the problem of expressing a big, specific question (can p vote in e?) into many smaller, easier to answer questions (is p 18?).
I believe there is always a more timeless truth at a layer of abstraction more general than where the problem is messy and volatile. This applies to natural domains as well as artificial. The trick is to find the timeless truth within budget and before the deadline 🙂 But if you do, it could be a serious competitive advantage.
Book update 👃
Wow! I think I might be done this week on Grokking Simplicity. It is being proofed for code correctness. Just a few more minor things to revise then off to production for indexing and layout.
You can buy Grokking Simplicity and use the coupon code TSSIMPLICITY for 50% off. You can buy it now and it will be shipped to you when it’s printed. Thanks to those of you who have already purchased 😘
Upcoming presentation 📢
December 5th I am speaking at re:clojure 2020. The lineup of speakers looks amazing. It’s all online so see if you can attend, too! Registration is free.
Presentation last week📢
Last week I presented at the Houston Functional Programmers User Group. I forgot to mention it ahead of time. But they’re such a nice bunch of people I wanted to shout them out. Thanks for the wonderful time! They don’t record presentations, so you have to catch them live.
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.
Also, if you just want to subscribe for a paid membership, I have opened them back up for the moment. Register here.
Stay healthy. Wash your hands. Wear a mask. Take care of loved ones.
Clojure Challenge 🤔
Last issue’s challenge
Please do participate in the discussion at the submission links above. It’s active and it’s a great way to get comments on your code.
This week’s challenge
In the Clojure Tip above, I described a kind of Boolean combinator that lets us build complex rules out of simpler rules. Your task is to build those combinators. Please define:
(defn rule-and () ([rule]) ([rule1 rule2]) ([rule1 rule2 & rules])) (defn rule-or () ([rule]) ([rule1 rule2]) ([rule1 rule2 & rules])) (defn rule-not [rule])
rule-or are monoids and so they follow the monoid pattern.
For a bonus, write the functions
rule-if (that implements the logical if arrow operator) and
rule-iff (that implements the logical if and only if double-arrow operator). Not that
rule-if is different from the programming
if since it returns
true if the condition is
Please submit your solutions as comments to this gist. Discussion is welcome.