PurelyFunctional.tv Newsletter 404: where to find timeless truths
Issue 404 - November 23, 2020 · Archives · Subscribe
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 person
and
election
? We don't know yet. To consider the can-vote?
signature
timeless, we must assume that person
and 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 person
and 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 person
and 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 true
/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:
person
contains birth dateperson
contains state of residencyelection
contains election dateelection
contains state
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
- 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
Boolean combinators
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])
Note that rule-and
and 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
false
.
Please submit your solutions as comment s to this gist. Discussion is welcome.
Rock on!
Eric Normand