is a possible case of CreepingFeaturitis
applied to language design. Idioms that can otherwise be more or less "emulated" using existing techniques are made formal language features. The motivation may be "brochure points bragging rights" rather than a real need.
The simplest example would be block-based control structure idioms: If-else, while, until, for, switch/case, etc. that can be emulated with a standard set of 3 goto commands: goto, if-goto, and labels. Few disagree with block idioms in general, but others are more controversial:
- Try/catch exceptions
- DesignByContract built-in features - can't be emulated unless you're talking about the cheap runtime version that is less 'design' by contract and more 'obsessively test invariants, preconditions, and postconditions after every operation'. In the latter case, emulation is easy.
- AspectOrientedProgramming - can't be emulated (unless you are the sole owner of ALL the libraries you're using)
- Built-in OOP DesignPatterns - such as DoubleDispatch over a subtype tree.
Similarly, one can even get rid of functions and subroutines (which can be emulated by gotos and a stack pointer), and of objects (which can be emulated via construction of tables of function-pointers), and of garbage collection (which can be emulated via explicit memory management), and of any other language feature one might name. That direction lies the TuringTarpit
This whole page thus far presents an argument on a slippery-slope in which the author is arbitrarily favoring the idioms he prefers and calling 'controversial' those he does not. Now, 'clutter' is a subjective and somewhat arbitrary concept in any case, so at least an argument sitting precariously upon a slippery slope isn't off-topic... but to be of value to people designing languages (or adding features to them), some discussion must to be given to much less subjective criteria
for deciding which 'idioms' ought to be included or excluded from a language.
The counterpoint discussion below proposes one such criterion: If a DesignPattern
common enough to earn that moniker cannot be refactored into and encapsulated by a language library, then the language is missing at least one feature. For lack of a name, one might term this the MissingFeatureSmell
. One can resolve this MissingFeatureSmell
by including the design pattern directly as a language feature, or by including a higher-level meta-language feature that allows one to capture that design pattern (and potentially a great many others having some properties in common with it) into a library. To avoid producing LanguageIdiomClutter
, the latter should be considered a better choice (at least for GeneralPurposeProgrammingLanguage
s). This MissingFeatureSmell
criterion is not subjective, though it does possess practical temporal dependence upon the extent of human knowledge regarding mechanism and what 'can' be encapsulated and refactored.
Above, 'language library' refers to any unit of source code that can usefully be developed and maintained independently of the programs that utilize it, and specifically excludes code generation utilities from outside the language.
As written, the MissingFeatureSmell
does not cover the cases when a feature should be dropped. In this case, it seems the converse might apply: A language can be said to possess LanguageIdiomClutter
if there is at least one feature that can be encapsulated in a library via use of the remaining features. This is also not subjective. Under this working definition, LanguageIdiomClutter
is a whole-language property and not a property of the idioms: for example, consider three features any two of which could produce the third within a language library. A language with all three features could be said to be cluttered, but you cannot point to any particular feature as the cause of the clutter (though you might point to the least useful feature when deciding which to remove, feature 'usefulness' remains beyond the prerogative of this definition of LanguageIdiomClutter
). As with MissingFeatureSmell
, this is not subjective but has a similar and practical temporal dependence on the extent of human knowledge regarding mechanism and what 'can' be encapsulated and refactored into language libraries.
as described above won't help choose the 'right' features (which is covered more by KeyLanguageFeature
s: utility across domains and avoiding GreenspunsTenthRuleOfProgramming
and permutations thereof). Nor does repeatedly applying the LanguageIdiomClutter
to remove features guarantee a minimum set of features (since it doesn't account for introduction of a new feature to absorb multiple others, and it doesn't help you choose which features to remove, it can only help you reach a 'minimal' set).
If the language lacks the ability to encapsulate a design pattern into a library, then implementing that pattern carries additional risk of error for every individual use case because you have the potential to make a mistake in the pattern in addition to the normal potential to err in whichever operation is being encapsulated by it. Often this additional risk of error is very considerable because implementing the pattern and proving it correct is non-trivial. Examples of such cases would include sorts, signal handlers, and memory structures; almost every programmer has at some point implemented his or her own linked list, but maps and hash tables... and ($entity
forbid) relational tables with multiple indices and access via query language, are all far beyond what most programmers would ever wish to implement by hand.
The Try/Catch exceptions mentioned above are a fine example of a pattern that could
be implemented via thread-local-data 'errno()' functions or expansion of return values to carry an error domain. They're also a fine example of a pattern that carries massive risk for error in the programming process: the tendency for programmers to handle errors in a YagNi
manner, failing to pass them back up the chain, and failing to properly handle resource release while doing so is very well known and observed. As such, exception handling instead of error-code patterns is a fine candidate for a language feature. It is perhaps not the most optimal such solution (exceptions with continuations would, IMO, be a nice touch), but it is a solution.
In any case, design patterns really indicate a LanguageSmell
- that the language lacks the capability to encapsulate a particular pattern, that functions and objects and classes and whichever other features the language has fail to do the job, that one can't refactor the pattern into a library and get it right once so thousands of other programmers don't have to repeatedly reinvent the wheel. Including the feature directly in the language is one option, but, if repeated too often, results in less a unified language and more a thousand features flying in close formation... like Perl. Where possible, language designers should seek to instead identify higher-level meta-features that allow one to refactor most or all design patterns (those known at language-design time and predicted in the foreseeable future) into libraries. RealMacros
, Templates or FirstClassTypes
or function pointers, and even ProceduralProgramming
with the original introduction of subroutines and functions, all qualify as cases of this sort of meta-feature.
When it comes to language design, it's either pay up front for this sort of feature or force every user (every programmer) to pay out the back in the form of re-implementing patterns and copy-and-paste programming and dealing with wizard-generated program templates. All that is serious language smell.
An advantage of LispMacros
, and anything like them, is that a LispMacro
can allow you to produce any syntax you want. That means that you, as a programmer, can create language-extending libraries. That means that the language-designer can stick a few basic features in and then implement the rest with libraries.
I am currently working on a programming language, and it will be like this: Just enough features so that you can have any feature you want.
See also: NonOrthogonalLanguageFeatures