PurelyFunctional.tv Newsletter 404: where to find timeless truths
Sign up for weekly Clojure tips, software design, and a Clojure coding challenge.
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
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
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
can-vote? signature. It is worth exploring whether we can
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
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
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
- Sort by content - submissions
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
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
if since it returns
true if the condition is
Please submit your solutions as comments to this gist. Discussion is welcome.