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)
- Polymorphism
- Control structures/loops
- Namespaces
- 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 langauge). 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.
Interesting . . . I’m sad so to say that even our best efforts with GScript will inevitably fall short of the sort of consistency you advocate here, but I agree that consistency is indeed an important consideration when it comes to syntax. I might argue that consistent syntax is more of a means rather than an end in and of itself, but that’s not really important: pretty much everything ends up being a means to “writing higher quality, more maintainable code faster” in the end anyway, even such seemingly ends-unto-themselves things as core readability.
That said, I think consistency is something people don’t often pay enough attention to; little things like the way annotation usages in Java can have named arguments and don’t use semi-colons just drive me a little nuts. Most languages have their own idiosyncrasies like that, of course, except for maybe Lisp: it’s a little Spartan for my taste, but it certainly has the benefit of being consistent. Smalltalk scores well on that point too, what with everything really being an object and all operations really being messages.
The Artima post my blog linked to was at least partially around DSLs, and my co-worker was arguing that DSLs actually make things worse largely because of the consistency issue; now you have a whole different set of syntactic rules to learn that differ from the rest of the language you’re programming in. Most of the time you really just want a really well-written library in your target language rather than a DSL so that you don’t have to learn yet another set of rules that only apply in particular circumstances.
favorited this one, dude
I’ve been studying Scala lately and it embodies many of these properties. It emphasizes programming in a functional style, yet also supports objects and traits (similar to Ruby modules). So far, I’ve encountered few “ad hoc’isms”. It has a consistent and elegant feel about it, even though it supports both functional and OO programming. I’m hoping it replaces Java as the primary, statically-typed language for the JVM. ;)
Admittedly it’s ugly, but I’m not sure you could accuse
class << self; self; enditself of being a special case – it’s entirely consistent with the way Ruby’s scoping works. Admittedly, Ruby’s scoping is weird, but the weirdness is applied consistently.