PurelyFunctional.tv Newsletter 407: two layers of design

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

Issue 407 - December 14, 2020 · Archives · Subscribe

Clojure Tip 💡

two layers of design

In John Ousterhout's A Philosophy of Software Design (see review below), the author suggests that the Java InputStream API is poorly designed. In order to read from a file, the standard, recommended way to do it is to write:

FileInputStream fis = new FileInputStream("inputfile");
BufferedInputStream bis = new BufferedInputStream(fis);

That is, you create a FileInputStream and, because you will want to read in blocks at a time for efficiency, you wrap it in a BufferedInputStream. Now you can read bytes from the file and be efficient.

Ousterhout suggests this is bad design because 99% of the time, the programmer will want to do this. They will need to write this same code every time and it is bad design to make the default way so complicated. He suggests that a better way would buffer file input streams by default, but allow for configuration in the rare case where you don't want buffering.

I do agree that this code is a real pain. I did Java at university and it was terrible. We had to write this so many times, it became burned into our fingers. We could type it without pausing. It was a bad design.

However, I also think it's a wonderful design. It separates the concerns precisely. That is evident because they are composable. So how is it that it's both a painful design and a good design?

The answer is that we need to think of design for two different purposes. One purpose is for programming with every day. You want something ergonomic and easy. The design should be human-centered. The other purpose is for programming with precision. Sometimes you're building tooling or some other backend system that needs precise control of the domain. In that case, you want the decomposed model so you can precision engineer exactly what you want. I will call this the two-layer design. One layer for programmatic usage and one layer for human usage. The human layer is built on top of the programmatic layer.

Java's FileInputStream design is good for programmatic usage. However, it doesn't have a second layer to make it easy to use. Ousterhout points out that it's bad design, but his recommendation is to start over with a more human design. I disagree. The programmatic layer is excellent design compared to most Java libraries. We should just build an ergonomic layer on top with useful defaults.

Another example of a failure to separate out the two layers is SQL. SQL is the main way to communicate with many databases. SQL is pretty good for humans to write. Yes, it could be better, but it's decent. However, anyone who has tried to programmatically build SQL statements knows how hard it can be. SQL statements are not composable. Although joins are a primary concept in the domain of SQL, you can't take two SELECT statements and programmatically make a join from them. It's an exercise in string concatenation and special cases. Where's the failure? They built the ergonomic layer but not the programmatic layer.

Separating out the programmatic layer has another benefit. We can build multiple ergonomic layers for different use cases. The idea that a single design could be optimal for all uses is beyond hubris. The best design pull things apart to be recomposed in new ways. We need to look past the surface ergonomics and into the deeper model the lower layer represents. That's where the real power of a design lies.

Awesome book 📖

A Philosophy of Software Design

I've been reading John Ousterhout's A Philosophy of Software Design. Ousterhout is the creator of Tcl (the programming language). He is a professor at Stanford and wanted to teach software design. The book is a distillation of his design recommendations, based on his extensive experience and the ideas his students needed to learn.

Overall, it's worth a read. I was hoping for a more first-principles approach. It feels very "rules of thumb". In the book and in his class, you learn design principles by doing. His guidelines try to point the way to a better design without defining it strictly enough for my taste. It's evident that the guidelines aren't strict because he present many exceptions. Rules with many exceptions is a symptom of the curse of expertise. In the end, he's a good designer but hasn't gotten to the bottom of it. But you can learn a lot from it.

Book update ✍️

If you're following along, you'll know that I submitted a final draft. That has been sent over to the copyeditor who is editing for grammar, spelling, and word usage. Simultaneously, someone is doing a design review to make sure every bit of spacing and font choice is consistent. I feel lucky to have keen eyes finding stuff. The book will be more awesome for it.

By the way, production's schedule has the book at the printer's on February 7. It's so close!

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 😘

Podcast episode🎙

I pontificated about whether Clojure is for early adopters. I think the perception is yes, but the reality is no. Clojure programmers, and Lispers more generally, are rather conservative. Same syntax since 1958!

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 406

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

Area of overlapping rectangles

Write a function that takes two rectangles and returns the area of the overlap. Sometimes the overlap is zero!

(overlap-area [{:top-left {:x 0 :y 0}
                :bottom-right {:x 10 :y 10}}
               {:top-left {:x 5 :y 5}
                :bottom-right {:x 15 :y 15}}]) ;=> 25

;; 2 identical rectangles
(overlap-area [{:top-left {:x 0 :y 0}
                :bottom-right {:x 1 :y 1}}
               {:top-left {:x 0 :y 0}
                :bottom-right {:x 1 :y 1}}]) ;=> 25

;; no overlap
(overlap-area [{:top-left {:x 0 :y 0}
                :bottom-right {:x 1 :y 1}}
               {:top-left {:x 6 :y 6}
                :bottom-right {:x 8 :y 8}}]) ;=> 0

;; enclosing rectangles
(overlap-area [{:top-left {:x 0 :y 0}
                :bottom-right {:x 1 :y 1}}
               {:top-left {:x -1 :y -1}
                :bottom-right {:x 2 :y 2}}]) ;=> 1

Thanks to this site for the challenge idea where it is considered Very Hard in JavaScript.

Please submit your design process as comments to this gist. Discussion is welcome.

Rock on!
Eric Normand