All Features Should Be Simple

RedHat: All programming language features should be simple!

BlackHat: But see SimplySimplistic and MissingFeatureSmell. AsSimpleAsPossibleButNoSimpler applies to language features and KeyLanguageFeatures, too. Making something truly SimpleIsntEasy. There is a certain amount of EssentialComplexity that each language feature must capture, lest they become a complexity multiplier by forcing every user to handle the essential complexity that wasn't captured.

BlueHat: You can still make them simple to understand and use.

BlackHat: Oh, you naive piece of headgear.... Maybe you can make good features simple to use after you understand them, but but SimpleIsntEasy to understand, either - ElegantSimplicity can be unintuitive, difficult to grasp, like attempting to capture water with a net. YouCantLearnSomethingUntilYouAlreadyAlmostKnowIt. Negative numbers are completely unintuitive to most children. Complex numbers are completely unintuitive to people who have only learned basic geometry. Monads are unintuitive even to most programmers. CategoryTheory is simple enough to make many budding postgraduate mathematicians go crosseyed.

RedHat: I can't use something I don't understand! The features need to be simple to understand!

YellowHat: Then learn something new! Grow yourself! Consider it an opportunity. You'll feel better and be better for it. Consider AlanPerlis, EpigramsInProgramming, Item 19: "A language that doesn't affect the way you think about programming, is not worth knowing."

WhiteHat: I learn.

BlackHat: We know.

RedHat: And just why should I need to change the way I think? Consider MindOverhaulEconomics. Maybe the machine and the languages should be adapted to the way I think. BlackHat: Riiiiight. And you believe the problems will be adapted to the way you think, too? And would you be willing to use or maintain languages built around the way, say WhiteHat thinks? If ThereIsMoreThanOneWayToDoIt it means you'll need to learn every which way for maintenance. Damn if you aren't a moron...

RedHat: Don't be so rude, you bastard! It was uncalled for. This conversation is over until you apologize! I'm never rude except when I'm in one of my 'reptilian' moods...

BlackHat: Apologize? Me? Hahahahaha! You're twice a fool if you think I'd ever do that.

GreenHat: Can't we all just get along?

WhiteHat: That is an innovative concept, but history suggests they cannot.

BlueHat: I think BlackHat might be correct about simple things not being so easy to understand. Looking back on it, I really struggled with fractions, and they seem easy now. But if this is the case, then how do we learn them and how do we discover them?

WhiteHat: I cannot say for certain. I believe we learn things of ElegantSimplicity through already almost knowing them upon first exposure (a condition that may elicit a EurekaMoment) or otherwise we learn through repeated application of simple concepts that we do not, at the time of application, truly grasp. It may be that fully 'grasping' any concept requires understanding its value and how and where it can be applied... or it could be that the mind simply balks at such a shift in worldview as to learn new concepts with extremely wide application; I do not know. I can say that in my own experience, learning Monads, I had to ape examples provided by other people to particular effect a great many times before the ElegantSimplicity of the monad and understanding of how and where it can usefully be applied really 'clicked'. I don't believe I can say different even for learning 'numbers' as a child - it is repeated application and word problems that really drive 'understanding' into my head.

BlueHat: Supposing WhiteHat and BlackHat are both right, it implies that we cannot understand 'simple' features that we do not use - they will not seem 'simple' to us. If we also wish to avoid use of features that we do not understand as RedHat suggests, I cannot help but think we will do naught but stagnate in our abilities to program more effectively using the features available to us.

BlackHat: Whatever. Let brutes be brutes; if they don't want to learn, I see no need to force them - just give them gruntwork jobs in non-critical software fields (like writing business reports or working on a VisualBasic team) then slap them whenever they say something stupid. Some of them will learn or reinvent 'simple' ancient ideas and create StupidXmlProjects and such, then later learn that they could have avoided the hassle because the language already did that (how many times have we heard in the last four decades "Lisp already does that"?). We've no reason to listen when morons like RedHat complain about features being there merely by virtue of their stagnant minds not finding them 'simple'. Following their 'logic' (*cough, cough*) to the end would have us all working in SimplySimplistic TuringTarpits?.

On TemplatesSmell, ChrisHandley expresses the opinion that all programming language features should be simple to understand. He is not alone in this opinion, but I'm skeptical.

A language consisting only of very simple features makes typical programming tasks easier, but hard problems harder. Every language that started out with much fanfare about its wonderful simplicity gradually accrued more complexity as it evolved and was applied to a broader array of programming tasks - Java [JavaLanguage] is an excellent example, and is only now starting to become rich enough to satisfy the needs of advanced users. This is why I look at most new languages with a jaundiced eye.

That's not to say that the Holy Grail of a relatively simple language that has the building blocks necessary for making complex abstractions can't exist; without being a real Lisp expert, I dare say that Lisp comes quite close to this, among the mature languages.

If you like, consider CeePlusPlus's template features as a separate language that integrates with the core language; only some programming tasks will warrant using this different language, and only programmers familiar with that language would be programming those layers in your system. (That sells C++ templates a bit short, though, since they're often useful for small, simple, tasks, too.)

ModulaTwo had an interesting take on this with its built-in SYSTEM module, that allowed you to do low-level manipulations only if you imported this module; otherwise the language was Pascal-like in its simplicity and safety.

-- DanMuller

See WhyPascalIsNotMyFavoriteProgrammingLanguage

Dan, thanks for explaining your view. I don't think there is any chance of us coming to an agreement, because this is as much a matter of principle as it is pragmatism. But I'm going to put across my view anyway! :)

I will grudgingly accept that some features cannot avoid being complex, but I think that such complexity should be fought at all costs. IMHO, CeePlusPlus is an example of what happens when complexity is embraced, rather than avoided if at all possible, while AmigaEe (although very old & missing a few things) & Java show how things can be kept simple yet still pretty powerful (elegant?).

One reason for avoiding complexity is that as complexity increases linearly, (a) the number of people able or competent in using it will drop exponentially, and (b) the time taken to reason about it probably increases exponentially. Not easy to prove, but I'd be surprised if I was totally wrong.

Another reason for avoiding complexity is that most people are lazy (like me), so we don't want to spend more effort on using something than is absolutely necessary.

But perhaps the biggest reason for avoiding complexity at all costs is that complexity begets complexity, by which I mean that a complex solution is likely to require even more complexity to be fixed or add new features. Also, if you use two mildly complex features together, the result is likely to be hideously complex. While I am sure some people love solving (or at least writing) such complex solutions, there is another related reason for avoiding complexity:

Each time you give-in to temptation (or just don't think) and use an unnecessarily complex feature, you may well have no problem using it, but what about the poor s*d who has to update/debug your code in 6 months time? If you are really unlucky, you will be that s*d (and have totally forgotten how your code worked)!

As I said, I don't really expect my explanation to change anyone's mind, because this is generally based on your own experiences & how important you rate various conflicting requirements.

-- ChrisHandley

Dan, it sounds to me like you've answered your own objection. How does Lisp fit into your skepticism about the value of simplicity?

Sure, individual systems tend to evolve to be more complex, but I don't think that means that the complexity is valuable or necessary; I think that a lot of complexity is caused by the inertia of people who don't want to abandon their system and move to a simpler, more powerful one.

There isn't always a simpler solution - but it's scary (or liberating, depending on how much inertia you have) to realize how often there is.

-- AdamSpitz

A large measure of the elegance of a language is how well it lets you express complex programs with a simple language. A kludged-together language ends up introducing a new language feature every time a program requires one. Knowing the language becomes tantamount to knowing every problem domain it's used for. PerlLanguage (and probably PL/1 [PliLanguage] before it) fits this pattern.

An elegant language tries to find similarities between the different requirements, and condense them down to a single abstraction. For example, subroutines free language designers from the task of including primitives for every possible operation, and end up allowing far more possibilities than the features they replace. Treating operators as functions (via OperatorOverloading, GenericFunctions, or MessagePassing) lets the language designer forget about primitives entirely - they become a library supplied (or open-coded) by the implementation. Blocks or LispMacros let the language designer do the same with control structures. Delegation (as in SelfLanguage) or first-class scopes (as in BetaLanguage or TransframeLanguage) let the language do away with functions, classes, inheritance, packages, and namespaces, using the same abstraction for each.

Unfortunately, programmers tend to require new language features faster than language designers can condense them down to simple abstractions. Keyword and vararg parameters are highly desirable, but there's no easy way to unify them with existing language features (though I've toyed with making all functions take a single argument, which could be any data structure, and providing lightweight syntax for literal sequences and dictionaries). AspectOrientedProgramming arose because all reasonably-complicated programs ended up with a significant number of cross-cutting concerns, and there was no language feature to centralize or abstract over them. GeneralizedReferences are very useful, but have very tricky interactions with other language features, such that only CommonLisp supports them.

-- JonathanTang

Ah, so many things to comment on here...

First, let's examine C++ a bit. Yes, the language has become quite complex - because it started out too simple, being built on C, which is a pretty simple language (if you ignore some idiosyncrasies of syntax that drive compiler writers nuts). In order to preserve the efficiencies of the underlying simpler language, e.g. being able to control memory use very explicitly by distinguishing between values and references, C++ is a bit hobbled. Stroustroup's TheDesignAndEvolutionOfCpp explains exactly why various choices were made, and although I don't agree with every one of them, they are all reasonable and justifiable choices. (Anyone serious about critiquing C++ should consider this required reading.)

I have read it, and it's interesting and informative, and yes, largely the decisions are understandable. However, the overall problem is that each decision was made myopically, not seeing the forest for the trees. Stroustroup attempted to keep things simple, and each year that went by forced him to realize that his previous attempts were literally impossible, and so he added more complexity to patch things up (and so too did the standards committee, for exactly the same reasons.

Basically it could have been a simple language if he had bitten the bullet and added just the right small amount of extra complexity early on, say circa 1982-1987. Here's one example of such a well-motivated but bad decision: "C++ compilation units should be linked with standard unmodified linkers." Nice idea. Failed in the end. The entire language was sabotaged in that valiant attempt.

The fact that the cfront compiler had to be completely abandoned in the end in favor of multiple written-from-scratch compilers is a very direct result of how the language outgrew early design decisions.

This may be one of the classic exceptions to YouAintGonnaNeedIt: that principle does not apply to general purpose programming language design (it probably does still apply to the implementation). You should in fact look down the road 10 years and make sure your current design decisions are compatible with future growth, even though that means overkill for the present, rather than wishing and hoping that minimalism might work.

-- DougMerritt

Another major limiting factor was the preservation of the separate compilation and linking model of C (and many other languages of the time). This limits or at least complicates possibilities for whole-program analysis, a technique that can let a compiler do more of the hard work of inferring things and thus allow the language to be simpler. Meyer chose that path with Eiffel, for instance, making it reasonable for Eiffel to make all methods virtual without incurring unnecessary run-time costs.

"Simple" has to be evaluated against your requirements, you see, and C++ has its roots in systems and embedded programming, where run-time efficiency is paramount. Perhaps one could argue that building higher-level abstractions like OOP and templates on this foundation and with these tight requirements was a mistake; some people agree with that, and some undoubtedly do not.

Now, I'll agree with few reservations that in the current hardware and software environment, this degree of efficiency is not usually needed. And if you hunt around the wiki a bit, you'll see that I have more than a few gripes with C++ myself, and no longer consider it my de facto choice for future projects.

Regarding Lisp, I'd say that the core language is amazingly elegant. But - the concepts on which the core is built are more complex than for many other languages. The notions of what a variable is, what code is, what equality means, what compiling (or reading) means, are all more complicated than in most other languages. Not much more complicated - but nonetheless too complicated for some programmers, perhaps even for the average programmer (whoever he is). This foundation is complicated enough, though, to form a solid basis for a whole host of more powerful abstractions without needing to fundamentally change the language.

But the relative simplicity of the language ends up being dwarfed by the complexity added by the libraries - standard libraries, mind you (I'm taking Common Lisp here) - which build on that core. And do you know why that is? Because a certain amount of complexity is required of a general-purpose language, and even if you come up with a relatively simple but flexible language like Lisp, you'll add the complexity back in with the necessary tools that are built on it. (SETF is a good example. Don't look under the hood...)

Don't get me wrong, I'm not complaining about the Lisp approach. In fact, it's far preferable to build all those tools from a simple but capable language, because it makes it easier to understand the tools. Oz [OzLanguage] is another example of a language that takes this approach, and is another language worth learning if you're interested in such things.

Finally, regarding advanced vs average programmers: there are real distinction in levels of expertise, you all know this - just look around your work place. You cannot write every bit of code so that it's easily understood by the lowest common denominator in your workplace. That would be incredibly limiting - nobody would create advanced frameworks and libraries that are easy for everyone else to use, why, nobody would write compilers for God's sake! (I hold compiler writers in very high esteem.) Some programming problems are very complex, and to solve them you have to have a very good grasp of the domain, and it helps immensely to have a language with powerful enough abstraction abilities to express the solutions succinctly. (Many of these hard programming problems also have the characteristic that they require good performance because of the amount of computation or data involved; I'm struggling with one such at work these days. Perhaps C++ isn't so bad after all...)

I think Chris summed this up nicely for me:

Another reason for avoiding complexity is that most people are lazy (like me), so we don't want to spend more effort on using something than is absolutely necessary.

For complex problems, sometimes what's necessary is a lot of effort. Now, should I spend more time writing a solution, hobbled by an overly simplistic language, so that people like Chris can spend that effort on understanding huge volumes of code? Or should I be able to write it efficiently using a more expressive language (or language/library system), so that Chris can spend his time learning some new language features in order to understand my more compact and elegant code? Frankly, I have little desire to accommodate lazy programmers, but I have plenty of energy for helping other programmers learn new things (and for learning new things myself).

I tossed this off rather quickly; I hope it expresses things accurately. Note that the last bit is really meant to be provocative (as in provoking thought), and not condescending. I don't really think that Chris is lazy, otherwise he wouldn't be instigating these discussions... but I know for a fact that there are indeed many lazy programmers out there, so the topic is definitely worth examining in this light.

-- DanMuller

I find it very annoying that I am expected to write a lot of dumbed-down least-common-denominator EarlyCeePlusPlus code because novice programmers won't understand "better" code. So we're stuck with the complexity and warts of C++ without being able to take advantage of its power. If people want to use a simple language, that's fine, but it is stupid to avoid useful features of whatever language you are using. -- anon

I agree, but who is it that's putting that limitation on you? Your manager? Or do you mean pundits who insist that anything not dumbed down is ThreeStarProgramming?

Limitations are from manager and other senior programmers (who themselves are not comfortable with LateCeePlusPlus-style code). Ironically, one of the reasons I was hired was for my expertise with "new" C++ language features, but I haven't had much opportunity to use that expertise. I do sneak some fancy stuff in from time to time, just to see what I can get away with, and there has been some positive feedback, but I'm still encouraged to stick with "C with classes"-style code. -- anon

View edit of August 14, 2008 or FindPage with title or text search