Alan Keefer has written on the benefits of clean, expressive syntax. He picks four main reasons that syntax matters: (fewer) lines of code, readability, memorability, and discoverability.
It is an excellent list, and I would like to add a fifth: rich consistency. By rich consistency, I mean two things: a sufficiently rich set of first-class objects, and the whole language there all the time.
Rich, Consistent Platforms
In a rich consistent platform, you have all the basic tools you need, and you can combine them any way you see fit, at any time. As an example, consider the following basic toolset. (This is not a "rich" set of tools, but it is plenty large enough to demonstrate the issue.)
- Encapsulation of verbs (functions)
- Encapsulation of nouns (objects)
- Control structures/loops
- Dynamic loading of code
Starting with our basic tools, we can mix and match. A simple example would be to write a method that loops over a collection invoking some operation. Our method is a function (1) attached to a noun (2) that contains a control structure (4) invoking a polymorphic method (3).
That's easy enough, and most modern languages can handle it. The problems start when you consider all the other possible combinations. Ask yourself if your primary programming platform can do the following. (Don't feel too bad if it can't: many platforms can't handle any of these.)
- Define a new verb (1) that acts as a control structure (4), usable in the exact same ways that the built-in control structures are used.
- Invoke a verb (1) polymorphically (3) based on more than one noun (1).
- Create new verbs (1) or new (kinds of) nouns (2) from inside a control structure (4).
- Switch or alias namespaces (5) inside a control structure (4) inside a verb (1).
- Change how polymorphic lookup (3) is resolved based on dynamically loaded code (6).
You may not be able to think of a real-world use case for all of these combinations. If the ones that seem realistic to you are exactly the same ones that your favorite platform supports, you should be very worried.
Impoverished, Inconsistent Platforms
What happens when your platform is not richly consistent?
- You see a proliferation of special cases. E.g.: Your language has control structures (duh!), but you are not allowed to use them in certain places. Instead, your platform provides a localized, ad hoc solution. Maybe you cannot write a loop to deal with aliases ("imports" if Java is your first language). Instead your language provides a wildcarding mechanism to help deal with namespaces. This mechanism only only works when dealing with namespaces, and nowhere else in the language. (This may seem minor, but multiply it by a few hundred and it creates a huge cognitive load.)
- Your platform is difficult to learn and remember, because of the special cases. Once you have learned the special cases, you become blind to these costs. Ruby's
class <<self; self; endcomes to mind.
- Instead of composing solutions using your basic tools, you create repetitive, redundant solutions via cut and paste. If your platform community is small, this is embarrassing, and goes by the name "spaghetti code". If your platform community is large, this is a point of pride, and is called "design patterns."
- Your community develops platform extensions to automate and conceal the most visible platform embarrassments. Often these extensions take the form of IDE automation for cut-and-paste tasks.
- Code is difficult and expensive to test. Test harnesses need to isolate code in ways not generally needed in production. Trying to write a thorough test suite will uncover some (but not all) of the ways that your platform is not richly consistent.
- Your platform loves code generators and aspect weavers to automate common idioms. Since consistency is not valued, these generators probably have their own syntax and rules, different from the core language.
Duct Tape, Anyone?
There is some good news. If your platform is large enough, then the open source community will rise to the challenge, and build a superstructure that deals with platform inconsistencies. The Spring Framework Mission Statement is a great example. It begins with:
We believe that:
- J2EE should be easier to use
- It is best to program to interfaces, rather than classes. Spring reduces the complexity cost of using interfaces to zero
- JavaBeans offer a great way of configuring applications
- OO design is more important than any implementation technology, such as J2EE
- Checked exceptions are overused in Java. A framework shouldn't force you to catch exceptions you're unlikely to be able to recover from
- Testability is essential, and a framework such as Spring should help make your code easier to test
The paean to OO design is mom-and-apple-pie, but the other five items can be summarized as "Is your poor, inconsistent platform failing for large, complex problems? Fear not. We have felt the same pain, and have packaged a set of workarounds."
The Spring team has done a good thing. I wish them well, and would bet money on their continued success. But "good abstraction over complexity" is not the same as simplicity.
We're Getting There
Programmers are moving to richer, more consistent platforms. But this movement could be much more direct. In general, we don't value consistency, or even notice its absence. But we do value killer apps, and they (slowly) drive platform choices in the right direction.
The next generation of software will be written on platforms that are more richly consistent. As a nice bonus, such platforms interoperate easily, so we might even hope that the days of platform monoculture are coming to an end.