raganwald
Sunday, April 29, 2007
  Writing programs for people to read
Programs must be written for people to read, and only incidentally for machines to execute.
Abelson & Sussman, Structure and Interpretation of Computer Programs


This is about writing programs in a style that favours human comprehension over the convenience of the machine.1

Norbert Winklareth recently raised the question of minimizing the semantic distance between the program as written and the solution to the problem as conceived by the programmer.

Norbert was talking about comparing the capabilities of programming languages, but the idea of semantic distance is also useful for comparing programs to each other. Although this is not the entirety of writing good programs, let’s examine this idea in more detail.

Indeed, let’s look at one very simple, very powerful, way of writing programs that are as semantically close to the solution in the mind of the programmer.

Code that resembles its result

A template is a blueprint for describing the result you want, where instead of embedding data inside executable code, you turn things inside out and embed executable code inside data.

Templates are very popular in programs that generate markup:


<HTML>
<HEAD>
<TITLE>Hello World</TITLE>
</HEAD>

<BODY>
Hello, Example. Today's date and time is <%=Now()%>.
</BODY>
</HTML>


It’s obvious what result you want, much more obvious than if you tried the following:


page = Page();
head = new Head();
title = new Title();
title.setText("Hello World")
head.add(title);
page.add(head);
body = new Body();
preamble = new StringBuffer();
preamble.append("Hello, Example. Today's date and time is ");
preamble.append(Time.now().toString());
preamble.append(".");
body.add(preamble.toString());
page.add(body);


This code produces its results as a side effect of its execution. The code itself doesn’t directly describe the result, whereas the first example directly describes the result we wish to generate.

Sometimes you need to generate the result as a side effect of the code. You needn’t write code as opaque as the answer above, instead you can organize your code so that its form resembles the form of the result you are generating, such as this Scriptaculous code:

element = Builder.node('div',{id:'ghosttrain'},[
Builder.node('div',{className:'controls',style:'font-size:11px'},[
Builder.node('h1','Ghost Train'),
"testtext", 2, 3, 4,
Builder.node('ul',[
Builder.node('li',{className:'active', onclick:'test()'},'Record')
]),
]),
]);


That produces this HTML:

<div id="ghosttrain">
<div class="controls" style="font-size:11px">
<h1>Ghost Train</h1>
testtext234
<ul>
<li class="active" onclick="test()">Record</li>
</ul>
</div>
</div>


Just like the template example, you don’t need to run a simulation in your head to try to figure out what the code produces.

Is this just cancer of the semicolon?

What’s the difference between these two code samples?


preamble = new StringBuffer();
preamble.append("Hello, Example. Today's date and time is ");
preamble.append(Time.now().toString());
preamble.append(".");


…and…


"Hello, Example. Today's date and time is #{Time.now}."


Is the second just syntactic sugar for the first? No. It’s more than just syntactic sugar. People have a habit of saying “syntactic sugar” in a dismissive way. It’s another argument that since an underlying language is Turing Equivalent, there is no need for a particular language feature.

Not all language features are just syntactic sugar. True syntactic sugar features are local features: you can replace the feature with some other equivalent code without having to change a bunch of stuff elsewhere.

Lazy evaluation and garbage collected memory management are not syntactic sugar: they require wholesale changes to the underlying model of computation to work. The abbreviated for loop in Java 1.5 is syntactic sugar: you can translate each loop into the equivalent old-style iterator loop without any additional support. For that matter, Java enums are also syntactic sugar, they’re a way to write the Type Safe Enum idiom with less boilerplate.

Okay, non-local features are not syntactic sugar. When is a local feature “just” syntactic sugar and when is it something more than that?

Let’s compare these two language features. Consider this Smalltalk code:


window
position: 80@80;
extent: 320@90;
backcolor: Color blue;
caption: 'My Blue Test Window'.


This shows a series of “cascading messages” to the same receiver. It saves you having to type the word “window” again, and it is a lot easier to read, because it lets you group messages that obviously belong together.

And earlier, we saw:


"Hello, Example. Today's date and time is #{Time.now}."


This is String Interpolation.2 It’s an “abbreviation” for a longer sequence of appends onto a StringBuffer.

The difference between these two trivial cases is that the first example doesn’t change your mental model of what’s going on when you read the code: you’re simply sending a bunch of messages to the window.




The Ruby Way is the perfect second Ruby book for serious programmers. The Ruby Way contains more than four hundred examples explaining how to do everything from distribute Ruby with Rinda to dynamic programming techniques just like these.


The second example is different in a very important way: honestly, when you read the second example, do you think “Aha! Start with a string, get Time.now(), append it to that string, then append a period”?

No way! You think “A String with Time.now() stuck in it.”

That’s a huge difference mentally, it’s not just shorter, it’s semantically closer to your mental model of the result you’re trying to achieve.

Wrapping up code that resembles its result

In summary, one way to write code that is comprehensible is to make sure that the form of the code matches the data the code generates. This is a very general principle, it can be found in web templates (like PHP and ASP pages), markup builder libraries, and even String or List Interpolation.

Features that support this style of writing code are more than simple syntactic sugar, because they alter the reader’s mental model, lowering the semantic distance between the code and the code’s result.

Bonus! Order now, and we’ll throw in these free Domain Specific Languages!

We saw that organizing code so that it resembles the result it generates lowers the semantic distance between the code and the solution. We saw two ways to do this: we can use templates or interpolation (if our language permits interpolation) to produce data, and where templates won’t work we can structure our code to resemble its result.

Well, we needn’t stop there. Domain-specific languages can provide this exact benefit.

The general purpose of a DSL is to write programs, or parts of programs, where the form of the code matches the mental model of domain experts or programmers. And here is one specific use for a DSL: to write code that closely matches the result it generates.

List Comprehensions model lists after mathematical notation. list { [x, y, x * y] }.given(:x => 1..12, :y => 1..12) directly describes a list of multiplicands and results.

(19|20)\d\d[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01]) is a regular expression that matches dates.3 Do you think it is hard to read? What would happen if you “compile” that expression into procedural code with side effects? Which program would be easier to understand, debug, and modify?

There are many valuable uses for DSLs. One of them is to create programs that closely resemble the results achieve, such as the HTML builder we see earlier, list comprehensions, and regular expressions. DSLs can increase human comprehension by representing the desired result directly.

And if you call in the next fifteen minutes, you’ll membership in the Pattern Matching family at no extra cost!

There’s another significant opportunity for writing code that increases human comprehension. We saw how to write code where the form of the code resembles its result. You can also write code where the form of the code resembles the data it consumes.

In ML, patterns allow us to make our functions resemble the different values they consume:

fun factorial 0 = 1
| factorial n = n * factorial (n - 1)


If you are not familiar with pattern matching, and especially with how languages like ML and Haskell combine patterns with their type checking system, maybe today is the day to spend a little time looking into this powerful idea for making comprehensible programs.



  1. In my personal experience, “favouring human comprehension” does not mean favouring readability over writability—in order to write a program that solves a problem, I have to understand the solution, so comprehensibility applies to the act of composing and of reading programs by humans.

    Now about machines: In this day and age, “The convenience of the machine” is often a way of saying, “the convenience of the layer of abstraction just below your program.” In a sense, every layer of abstraction above the silicon is a kind of virtual machine.

    “Remember, it’s all software, it just depends on when you crystallize it.”—Alan Kay, as quoted by Andy Hertzfeld

  2. List Interpolation actually predates String Interpolation, but most people recognize String Interpolation. Lispers have a little thing called a quasiquote or backquote that builds lists or vectors in a template form.

  3. Early feedback suggested this is a poor example of a regular expression, because it looks obtuse. I could have selected something much simpler, however I wanted something that really would be incredibly obtuse if you tried to code it procedurally. (Not counting using built-in library functions for parsing dates, of course).

    The point is that this regex is readable, and you can see out all of the special cases, right where they belong in their place in the pattern. If readers can post some imperative code that does the same thing in a more readable form, that would be a very interesting lesson.

Labels:

 

Comments on “Writing programs for people to read:
> If you are not familiar with pattern matching, and especially with how languages like ML and Haskell combine patterns with their type checking system, maybe today is the day to spend a little time looking into this powerful idea for making comprehensible programs.

You don't need Haskell's or ML's type system to make Pattern Matching work, and work extremely well.

Pattern Matching is rampant in Erlang (hell, even what other languages usually see as assignment is a special case of pattern-matching in erlang), yet the language is dynamically typed


PS: blogger comments blow: even with a google/blogger account, I have to enter a captcha whose image doesn't even appear in my firefox for no known reason.
 
Minor nitick: Nobody in the Smalltalk community upcases the "t". I now feel like a Lisp weenie bitching about "LISP".
 
Hey Reg,
Great article like so many of your others but I do have to point out that "Smalltalk" is spelled with a lowercase "t". Keep them coming!
 
Can you please linkify your footnote references? It's pretty tedious to scroll down the whole page to find the first footnote, read it, then get back up to where I stopped.
 
Hi, I tried to describe problems with the syntax of lighttpd.conf... in essence my problem was to guess how to write stuff so that lighttpd understood what I meant:

http://typo.cdlm.fasmz.org/articles/2007/04/25/greeting-ipv6-users-with-lighttpd
 
Can you please linkify your footnote references

Sadly, I use the absolute worst blogging software I've ever seen, Google's Blogger.

It can't do footnotes, it acts as if Trackbacks don't exist, and as far as I can tell, it is written to cater to the needs of sploggers so that they can use misappropriated content to drive adwords revenue.

Which is probably why it doesn't do footnotes: Google hasn't figured out how to attach ads to them :-)
 
I think regular expressions as they are expressed as you show above is an example of how a DSL-ish representation can still be really obtuse. Regexes have always looked like line noise to me.

I prefer reading and writing regexes with a more natural language, "fluent" interface.
 
Very interesting post!

About syntactic sugar. Even if syntactic sugar "just" adds simplicity (a la the simplified for loop) instead of new semantic meaning, it still matters. Because as you mention it's not about Turing completeness, it's about changing how people think and use things. Doing something has a certain friction related to it, and once you reduce that friction to below the threshold of being annoying or bothersome, you can fundamentally change the way people behave.

Long ago, I attended a talk about Perl 6 where Damian Conway talked about Syntactic Vitamins, and I think that perfectly captures the power of making common things really easy to do. This line of thinking seems to be pervasive in the design of Perl 6 (and as for irony and the ability of Perl to Get Things Done(tm): note the age of my post :)

About DSLs. I actually thought of this tired TLA already when you mentioned templates and string interpolation, because if you think about it that's what string interpolation really is: a very very simple notation for accomplishing something within it's domain. Not that it really matters if you want to cram it in under the DSL label, but putting stuff inside strings is such a common thing to do when programming that you wonder how people can design new languages without even considering something like this. I mean, the concept has been obvious since the printf/sprintf syntax became mainstream with C.
 
If you apply the logic of the article to higher level business constructs you should, it seems to me, drift immediately into business rules. The use of a business rules management system to specify "business rules" rather than a low level coding language would be compelling for the same reason that better laid out/written code would. Helping those who need to validate/manage the organization's policies and procedures interact directly with "code" they could understand could only help.
The use of business rules could make your code more maintainable, reduce your dislike of change and reduce your application knowledge defecit.
JT
www.edmblog.com
 
If you apply the logic of the article to higher level business constructs you should, it seems to me, drift immediately into business rules.

That's a slightly different application of DSLs. It's possibly even more valuable, but I was trying to stick with one very simple principle and show it is a lot if different contexts.

Thanks for the links!
 
Business rules are one of those things I find really frustrating, because they almost work.

Pulling out the rules and expressing and storing them in a clean declarative way seems like a really good idea, for all the obvious engineering reasons. But the trouble is that the order rules fire in matters, because they can update the state of your system. Then you end up messing up your nice declarative framework with all sorts of hideous hacks to treat ordering.
 
Since you asked for other ways to write this, here's how this looks in my work-in-progress language Flan:

@separator = oneof "- /.";
@date = [
/some(1900..2099).toString,
separator,
/some(1..12).toString,
separator,
/some(1..31).toString
];

(Ok, this doesn't handle the initial '0's.)
 
I've new to reading your blog, but I've noticed that you reference the SICP pretty regularly. You usually point links to the Amazon page, so I thought I'd mention that the SICP is available in full on the net. You could even link to the page you quote. If this was widely known information, pay me no mind...
 
Matt:

I have linked to the free version quite often, such as on Irony and The first seven books I would buy if my shelves were bare. But another link is helpful.

Personally, I love physical books, especially timeless classics like SICP. But the free version makes it possible to try before you buy, makes it accessible to those on a budget, and generally makes the world a better place.

Rock on!
 
One can design programs to be read using tools rather than changes to a specific language/platform. Check out Literate programming, coined by Don Knuth when he created web. The state of the art, IMO: Leo. It doesn't waste effort on paper rendering like Knuth did with all the latex.. The critical ability is simply ordering code for readability rather than a compiler.
 




<< Home
Reg Braithwaite


Recent Writing
Homoiconic Technical Writing / raganwald.posterous.com

Books
What I‘ve Learned From Failure / Kestrels, Quirky Birds, and Hopeless Egocentricity

Share
rewrite_rails / andand / unfold.rb / string_to_proc.rb / dsl_and_let.rb / comprehension.rb / lazy_lists.rb

Beauty
IS-STRICTLY-EQUIVALENT-TO-A / Spaghetti-Western Coding / Golf is a good program spoiled / Programming conventions as signals / Not all functions should be object methods

The Not So Big Software Design / Writing programs for people to read / Why Why Functional Programming Matters Matters / But Y would I want to do a thing like this?

Work
The single most important thing you must do to improve your programming career / The Naïve Approach to Hiring People / No Disrespect / Take control of your interview / Three tips for getting a job through a recruiter / My favourite interview question

Management
Exception Handling in Software Development / What if powerful languages and idioms only work for small teams? / Bricks / Which theory fits the evidence? / Still failing, still learning / What I’ve learned from failure

Notation
The unary ampersand in Ruby / (1..100).inject(&:+) / The challenge of teaching yourself a programming language / The significance of the meta-circular interpreter / Block-Structured Javascript / Haskell, Ruby and Infinity / Closures and Higher-Order Functions

Opinion
Why Apple is more expensive than Amazon / Why we are the biggest obstacles to our own growth / Is software the documentation of business process mistakes? / We have lost control of the apparatus / What I’ve Learned From Sales I, II, III

Whimsey
The Narcissism of Small Code Differences / Billy Martin’s Technique for Managing his Manager / Three stories about The Tao / Programming Language Stories / Why You Need a Degree to Work For BigCo

History
06/04 / 07/04 / 08/04 / 09/04 / 10/04 / 11/04 / 12/04 / 01/05 / 02/05 / 03/05 / 04/05 / 06/05 / 07/05 / 08/05 / 09/05 / 10/05 / 11/05 / 01/06 / 02/06 / 03/06 / 04/06 / 05/06 / 06/06 / 07/06 / 08/06 / 09/06 / 10/06 / 11/06 / 12/06 / 01/07 / 02/07 / 03/07 / 04/07 / 05/07 / 06/07 / 07/07 / 08/07 / 09/07 / 10/07 / 11/07 / 12/07 / 01/08 / 02/08 / 03/08 / 04/08 / 05/08 / 06/08 / 07/08 /