PurelyFunctional.tv Newsletter 404: where to find timeless truths

Sign up for weekly Clojure tips, software design, and a Clojure coding challenge.

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 date
  • person contains state of residency
  • election contains election date
  • election 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

Issue 403

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 comments to this gist. Discussion is welcome.

Rock on!
Eric Normand