The High Risk of Novel Language Features

November 21 2006, last updated January 26 2008

It is quite fashionable to bemoan the lack of adventure in todays programming languages. Outside of a few esoteric languages which seem to be different only for the sake of it, most languages in real use today would be recognisable to programming language researchers from the late 1960's. Most such languages have few, if any, genuinely novel language features in them; what differentiates them are seemingly minor, but nevertheless important, things such as syntax, libraries and - most elusively - the general feel. In fact some languages make virtues of this: until recently Python was proud of the fact that it had only one novel language feature, on the basis that it contained only those constructs which had proved themselves in prior languages.

The obvious question is: why don't different programming languages have novel language features? I think the reason can be most clearly seen by example. When Java appeared it had precisely one novel language feature - checked exceptions, a language feature which has no precedent. Personally when I think of the single worst feature of Java, the feature which most turns me off the language, it is - wait for it - checked exceptions. By trying to enforce good programming practice, they make life so annoying that many people actually use empty catch blocks, which means their code is less reliable than it would have been without checked exceptions. I know that I'm not alone in thinking that this is a fundamentally bad thing. Here you have the quandary: novel language features carry with them an exceedingly high risk. Most novel language features turn out to be one or more of irrelevant, dangerous, or annoying; checked exceptions come under the latter two categories. Novel language features carry with them such a high risk of failure that most sensible language designers avoid them whenever possible.

Earlier, I said that (until recently) Python had only one novel language feature. It's a small, rarely used feature: the else clause on for and while loops. One uses this as follows:

for ...:
  ...
else:
  ...
When the for loop terminates naturally (i.e. the loop condition is no longer true), the code in the else clause is executed. If, however, the loop is terminated via a break or return statement, the else clause is not terminated. Such a small feature perhaps justifies those who think that modern languages don't contain any novel language features, but at least this particular feature isn't dangerous or annoying.

Until very recently - and assuming one discounts the novel language features related to compile-time meta-programming and DSLs - Converge also contained only one genuinely novel language feature. You may well be able to spot its lineage however: I refer of course to the exhausted and broken clauses on for and while loops. In Converge one can use these as follows:

for ...:
  ...
exhausted:
  ...
broken:
  ...
When the for loop terminates naturally, the code in the exhausted clause is executed; if the loop is terminated via a break statement, the broken clause is executed. Loops may specify neither, either, or both of these two clauses. That's it. It's novel (or, at least, I believe it to be novel) but one could hardly call it exciting. Really, it just captures a common usage idiom, uses a more sensible name for Python's else clause and provides the matching broken clause (the latter being the novelty). Despite the relatively conservative nature of this novel language feature, I was still very nervous that it would be a failure as most novel language features have been before it. Several tens of thousands of lines of Converge code later, I can now say with something approaching confidence that it's been a successful addition: it's useful, it's fairly obvious to use, it saves typing out a standard error prone idiom, and it integrates well with the rest of the language.

However, for many months I have been aware that there is an idiom in Converge that has sullied my code. Here's an example of the idiom:

X := 0
Y := 1
  
...

if a == X:
  // do something related to X
elif a == Y:
  // do something related to Y
In other words, there is an enum of sorts (represented by constants in X and Y) and an if statement, each branch of which copes with one of the enums cases. The problem is that if someone later adds to the enum the if statement doesn't execute any code at all, masking a serious error. I've tended to get round this by adding an else clause as follows:
else:
  raise "XXX"
However this is undesirable since I normally use this idiom to mean not implemented yet whereas here it's more of an assertion saying shouldn't have got here. In a dark, dank corner of my mind the solution for this seemed to be that Converge should grow a switch statement, where the default action for the switch would be to throw a shouldn't have got here exception of some sort.

A couple of days ago, when programming something with lots of enums, I realised that I had to implement something to prevent my code from being sullied further with this idiom. So I started implementing a switch statement. Half way through adding an entirely new construct to the language, I had a revelation: what if there was a variant of the if statement whose default else action was to raise a shouldn't have got here exception? And so, with some trepidation, but also a slight sense of impulsion, I implemented such a feature. It's called No Default If or ndif and for the above example one would write:

ndif a == X:
  // do something related to X
elif a == Y:
  // do something related to Y
If none of an ndifs branches match then an exception is raised. In this case this means that if a is not equal to X or y an exception will be raised, and the programmer made aware that they need to augment their code at the appropriate point. By definition, it makes no sense for an ndif to have an else clause.

So there you have it - the base Converge language has its second novel language feature. I hope it will be useful, but I'm far from sure. For the next 18 months or more, I will worry frequently as to whether ndif will share the same fate as Java's checked exceptions. What happens if I've polluted my nice language with a disastrous feature? The next time that you complain that languages don't contain enough novel new features, try and remember the language designers quandary.

Updated (January 26 2008): In fact, ndif might not be as unique as suggested in this article - Erlang's normal if statement acts similarly to ndif. Erlang's exception-raising can be turned off by adding the equivalent of elif true: pass. This would appear to represent a fundamental difference in language design: Converge's if caters to the common (at least 95% in my experience) case, whereas Erlang's appears to optimise the less common case. My thanks to Thomas Figg for pointing me at this facet of Erlang.

Follow me on Twitter

 

Blog archive

 

Last 10 posts

What Challenges and Trade-Offs do Optimising Compilers Face?
Fine-grained Language Composition
Debugging Layers
An Editor for Composed Programs
The Bootstrapped Compiler and the Damage Done
Relative and Absolute Levels
General Purpose Programming Languages' Speed of Light
Another Non-Argument in Type Systems
Server Failover For the Cheap and Forgetful
Fast Enough VMs in Fast Enough Time