In Succinctness is
Power, Paul Graham ponders why Python — or any language
— would choose readability over power. He isn't sure
what is meant by
regularity, and discusses several
readability, but never gets to what I think is
the core of the what Paul Prescod meant by his statement.
Readability isn't simply about the line or character
counts that Paul talks about. It's about how quickly you can pick up
code that someone else wrote, or that you wrote six months ago,
understand it, and fix a bug or add a feature. Paul's discussion of
programming features focuses on how easy it is to write code to do
new things. If you're developing new code, that's what you want. On
the other hand, if you're maintaining code, the hard part is usually
figuring out what the original coder was up to, not making the
required changes. In this latter case, readability is more important
than power. In extreme cases, it's easier to rewrite the code from
scratch than to figure out what it's doing. In fact, this is one of
the rules Kernighan and Plauger's Elements of programming
Don't fix bad code, rewrite it.
Readability also helps when the goal is to explain new things to
other people. If you're planning on publishing the code to
demonstrate or document a new algorithm of some kind, then readers
need to be able to pick it up and understand it. A
feature that saves you 60 minutes of development time but costs
thousands of readers a minute or two each to understand is a net
Regularity is part of this. If a language is regular, then
you can expect that if some language feature works for some type in
the language, it'll work for all similar types; that all statements
will be terminated in a similar manner; that function arguments will
be evaluated in the same order everywhere; that — well, the list is
potentially infinite. A language being
readability by making it reasonable to infer the behavior of
things you aren't familiar with based on the
behavior of things you are familiar with, which saves you time when
reading a program.
Paul argues that a language can't be too powerful, because the author can always write things out in the less powerful idiom if they don't like the more powerful one. The reader doesn't have that option. If the author is allowed to choose the more powerful option and does, then the reader has to read — and understand — the more powerful version.
Some people claim that a language is powerful if it has lots of features, so that whenever you need to do something, you have lots of ways to choose from for achieving the task at hand. This is in line with succinctness being power, in that if you get to choose between lots of ways to do something, you can chose the most succinct way. This form of power also leads to a less readable language. Some very popular languages have so many features that few, if any, people actually program in the entire langauge. Almost everybody programs in a private subset of that language — their dialect. So when you sit down to read a piece of code, you have to translate from the authors dialect to yours — which may require consulting the manual if it's sufficiently different from yours. In the worse case, you'll be reading code that was written by one person and then maintained by others, so you may well be switching between multiple dialects in reading a single piece of code — meaning that two apparently different bits of code may well provide identical functionality, but the dialect is different.
Let's look at some language features to see how power and readability trade off. We're going to look at one area of language design: passing arguments to a function. Functions are one of the most powerful features of modern languages. They allow code to be reused in multiple places. The arguments to the function allow the same code to be used with different data. Functions also improve readability. Once you read and understand a function, you don't have to do it again. If the author failed to use functions, but simply repeated the code each time, you'd have to read the code to verify that it's actually the same in each case. So functions are pretty clearly good for everyone involved. In particular, we're going to look at one task: figuring out how a variable in our source code gets the wrong value.
Let's start with the least powerful version of arguments, and one found in many modern languages. The language simply evaluates each argument, and passes the resulting value to the function. The function can't change the value of any variables named in the arguments. This is a very readable construct, as if you're trying to figure out what happened to a variable, you can ignore the function calls, because you know they didn't change the variable.
Now let's add a feature: let's let a function be able to change the value of a variable passed to it. This provides a more succinct way of using a function that changes the value of a variable. You can simply write:
def inc m m = m + 1 x = 3 inc x inc x inc x print x
And it would print 6. If inc can't change the valaue of it's argument, then the equivalent code would have to be written something like so:
def inc m return m + 1 x = 3 x = inc x x = inc x x = inc x print x
Each of the lines that comprise the function has twice as many symbols. More complex examples, involving changing more than one variable and possibly having a return value from the function as well, are much worse. So this new feature make the language more succinct, and hence more powerful.
From the readability standpoint, this is a disaster. Now you have to read every function that the variable you're interested in is passed to, because they may well change it. The amount of code you've got to examine has gone up by an indeterminate amount.
So if your language prefers readability to power, you'd want to give some serious thought to whether or not you want to add this feature to your language. If you do so, you will probably eventually realize that the problem is that there's nothing at the point the function is called to note that the variable could be changed. If you do things that way, then you only need to read functions that are passed variables flagged as changeable at the call point. The language has the same number of features as before, is only slightly less succinct, requiring one more symbol than the original version.
Now that we can change a variable passed to a function, let's
look at the case where the variable of interest is passed to
multiple function that are allowed to change it, and the values
returned by those functions are passed to a function. That isn't
inherently harder to read, until you ask the question
are the arguments evaluated? If the language specifies an
evaluation order, then you can read the functions that might change
your variable in that order, and ignore the function those
functions return values are passed to, unless it has your variable
as an argument. If the language doesn't specify the order of
evaluation, then you have to consider each possible calling
order. But let's make our language really powerful! Let's
let the called function decide not only what order the arguments
are evaluated in, but allow it to evaluate them multiple times,
including not at all if it decides that's appropriate. Lisp macros
have this ability, and it's part of what makes Lisp programmers
hate programming in languages without them. The invocation syntax
is the same as functions, so for the purpose of reading code that
uses them, they are the same as functions.
From a readability standpoint, this is a disaster of the same
order as letting functions change the values of variables passed to
them. It's not quite as bad, because it only affects functions
which are called as part of evaluating arguments to other
functions, which may or may not be all that common. But every time
you examine such a case, you have to read through the outer
function to figure out whether or not the function call you're
actually interested in is evaluated. Worse yet, the answer may be
maybe, as it will depend on the value of some variable in
this new function. So now you're starting over with a different
function and variable.
While these last examples are exaggerated, and real-world LISP
programs are generally better written than that, the point is not
to say that these features are bad, but to explain how
powerfull is not the same thing as
more readable, and
provide some motivation for preferring the latter to the former. I
don't expect to change anyones mind about what programming features
— and hence languages — they prefer. I hope I've helped
someone understand why other people might prefer readability
— and a language that values it.