Pouring water back into the flask
The University of Flatland uses a novel two-part practical test to determine whether Comp. Sci. undergraduates should be steered into practical programming or abstract theory streams. In the first part, the students are given a beaker, a bunsen burner, a stand, and a flask of water. They are told to boil water. Naturally, they fill the beaker, place it on the stand, light the burner, and are queued up to perform the second test.
In the second part of the test, they are presented with a beaker of water sitting on a stand over a Bunsen burner, and an empty flask. Most of the students light the burner and are led away to begin their studies in programming. But a precious few disassemble the apparatus and pour the water back into the flask, reducing it to a problem they have already solved. They are led away to begin the long road to their Ph.D. in Lisp, Recursion, and Category Theory.
Like most the jokes I retell, this is not particularly funny. But let’s talk about “Design Patterns” and then come back to it. Design patterns allow developers communicate their intentions to each other with a common vocabulary.
This makes sense. If I create a Flyweight and I want to describe it to another developer, having a word for it, along with a common understanding of what problem a flyweight solves, streamlines our communication. Design patterns in that light are jargon, a sub-language used by specialists to discuss their speciality.
If it stopped right there, you would have the design patterns invented by Christopher Alexander and articulated in the incredible book
A Pattern Language.
But it doesn’t stop there. In certain programming cultures, people consider Design Patterns to be a core set of practices that must be used to build software. It isn’t a case of
when you need to solve a problem
best addressed by a design pattern,
then use the pattern and refer to it by name. It’s a case of
always use design patterns. If your solution doesn’t naturally conform to patterns, refactor it to patterns.
Is this madness? Yes… And no…
Consider
Scheme. Everything in Scheme is built out of just
five primitive “special forms.” It is positively
Forth-like in its economy of power. So it is clearly possible to build very powerful programs out of five primitives. So why not build programs out of thirty-five patterns?
While we digest that, back to the joke. Scheme programmers are clearly the impractical theoreticians, aren’t they? Reducing everything to their five special forms that they have already solved and what-not. While the practical programmers look for the simple, direct way to boil water.
When you take a problem with a straightforward solution and make it more complex by re-expressing it as a combination of patterns, you are pouring the water back into the flask.
So what do we make of the “everything should be one of these thirty-five standard design patterns” argument? I make of it the same thing that I make of the joke. It is clearly possible. But when you take a problem with a straightforward solution outside of the core patterns in one book and make it more complex by re-expressing it as a combination of patterns, you are pouring the water back into the flask.
The argument that “everything should be one of these thirty-five standard design patterns” is an argument that fits the theoretician, not the pragmatist. It is motivated by a desire for building very complicated things out of very simple parts. That is possible. But it is a fallacy to believe that simplifying the constituent parts simplifies the software. Like boiling water, you can make it
more complex when you place the pattern ahead of the solution.
Of course there are arguments ad nauseam about standardization and readability. But I have this strong suspicion that at the core of it, the motivation is a belief that the world should be reduced to a simple set of easy-to-understand things that can be combined and recombined into complex solutions.
And Scheme programmers? You may have noticed that although Scheme programs are built out of the five special forms, Scheme programmers do not write everything in the five forms: they use abstractions like continuations, macros, and functions to write expressive and powerful programs.
If you re-wrote a complex Scheme program in the five primitives, would it really be easier to understand because one programmer could describe it to another using just five words in their common vocabulary?
This is a follow-up to
Newly Discovered Design Pattern: “Code Well.”addendumScheme’s five special forms: The ones I was thinking of are
define
,
lambda
,
if
,
quote
, and
set!
. And I’m not convinced you need
define
. This is from memory, and furthermore just because you
can build everything from a few primitives doesn’t mean that that’s how the implementation works. Smalltalk took this aggressive approach to building Smalltalk in Smalltalk, but I am not immersed in Scheme, I do not know what current implementations actually do.
If you do a little Googling, you will find that people often refer to constructs like
let
as special forms, because they are not function calls. However, they are not
primitive special forms because you can build
let
out of
lambda
and function calls.
Given the primitive special forms, you also need a library with functions like
equals
,
car
, and
cdr
defined. Between the primitive forms and the primitive library, you can define
eval
metacircularly and
define-syntax
for all of the other special forms. From there, you can build a modern Scheme in Scheme.
(There may be some other good choices for building “a Scheme.” You may want to consider
call/cc
a primitive special form. If you don’t, you have to rewrite function calls (probably using CPS), and this means you are no longer running your Scheme programs in your primitive Scheme, but rather in your evaluator.)
And furthermore…Keith Braithwaite’s
critique.