Nemerle's "when": Bad Idea, Easily Solved


It's no secret I'm a big fan of D, and very critical of most other languages. But there is another language I actually do like: Nemerle.

Some highlights of Nemerle

Like D, Nemerle is one of the few languages which truly "gets" metaprogramming and provides a decent implementation[1] (unlike, say, C# which only offers a half-baked generics system plus a dynamic type and simply leaves it at that). D and Nemerle's approaches to metaprogramming are entirely different and both have their pros and cons, but I like them both.

Another thing I love about Nemerle is its powerful, yet extremely accessible, algebraic types and pattern matching. This is actually one area where Nemerle is vastly ahead of D: While D does offer algebraics via its standard library, they're nowhere near as nice and clean as they are in Nemerle.

Nemerle (as does D) naturally has its downsides, though. For one, it lacks D's fantastic "ranges and algorithms" system. Also, like C#, Nemerle's reliance on CLR (ie, Mono/.NET) renders it medicore at best for low-level bare-metal work. And unlike Nemerle's designer's, I remain entirely unconvinced that merging the ideas of "statement" and "expression" leads to an cleaner, nicer language. I realize the common statement/expression separation isn't necessary and hasn't always existed, but I happen to like it, and I find it more useful than problematic. I don't like implicit returns, and as for the benefits of blurring the statement/expression line: Having lambdas, the ternary operator and an expression equivalent to switch blocks are all I've ever desired. The merging just seems unneccesary and overboard. But that said, I can certainly live with it just fine.

Overall, D is still my preferred language. But Nemerle is the only other language I can say I genuinely like.

Nemerle's one big blunder

For all its good, there is one big thing about Nemerle that's been bugging the hell out of me: The if statement requires an else clause. If you don't want else, you say "when", not "if".

That may seem minor, and the Nemerle designers do have a very deliberate reason for it: "to avoid stupid bugs with dangling-else". They provide the usual old dangling-else example that relies on combining bad indentation with omitting brackets on multi-line blocks.

I have a problem with that reasoning.

In language design, any feature or deviation from common expectation needs to pull its own weight. The benefits must outweigh the costs. Let's see:

Benefit: "Eliminate stupid dangling-else bugs": Mmmm hmmm. Ok. So....Who the fuck actually hits this issue?!? I've been coding for twenty-five years. As such, I firmly believe that stupid mistakes happen, to even the best of programmers, and that the true mark of an amateur is believing you can (or have) overcome that. I openly admit I've made, and will continue to make on a regular basis, every stupid freaking mistake in the book...except this one. I have never made this one. I have never even seen this one get made by anyone, anywhere. I admit, I really do have no doubt it does get made. Sure. Occasionally. On rare occasions. But heck, who the fuck ever omits the curly braces around a multi-line "if" body anyway (a requirement for this bug to occur in a whitespace-insensitive language)? If it does happen, who the fuck lets it pass code review, whether dangling-else bug or not?

Is the benefit of eliminating this rare bug worthwhile? Naturally, that depends on the cost:

Cost: Take one of the most basic, common, fundamental constructs in all of programming, modify it, rename it, and force every addition/removal of every "else" clause anywhere to always involve swapping between "if" and "when" on a different line of code.

So, a major disruption to one of the most fundamental constructs in order to eliminate a bug that's quite frankly more theoretical than real? No. No Nemerle, that is not a good tradeoff.

Solution

Luckily, Nemerle's biggest problem also demonstrates one if it's greatest strengths: This design flaw is easily changed, in mere user code alone, without hacking or modifying the language or compiler at all:

macro plainIf(cond, body) syntax("if", "(", cond, ")", body) { <[ when($cond) { $body } ]> }

That's it. That solves it. Those few lines bring back a normal "if". Just toss that into a "plainIf.n", compile to a DLL:

ncc -r Nemerle.Compiler.dll -t:dll plainIf.n -o plainIf.dll

Then toss that DLL into your project:

ncc -r plainIf.dll [...the rest of your args here...]

And magically, you can use "if"s with or without an else. Nice.

For convenience, I've put this code (along with Posix/Windows buildscripts and a small test program) up on GitHub, in a simple project named plainIf.


[1] Ok, I realize there is *ahem* a certain enthusiastic subset of programmers who will look at Nemerle's macros and bring up the ol' "Everything is LISP" idea. And y'know what? They may well be mostly right. But, if I want to drown in a torrent of barely-readable symbols, I'll just use brainfuck and get to the "shoot myself" stage that much quicker.

2 comments for "Nemerle's "when": Bad Idea, Easily Solved"

  1. (Guest) jon
    2015-05-21 00:52

    The mandatory 'else' is Nemerle's functional language inheritance showing: FWIW, it's the same in Haskell (http://en.wikibooks.org/wiki/Haskell/Control_structures ) and Standard ML (http://en.wikibooks.org/wiki/Standard_ML_Programming/Expressions#Conditional_expressions ).

    The reason for it is that you can write something like the following in Nemerle, which a lot of C-heritage languages won't allow (It might be interesting to see whether this kind of statement will still compile in the presence of your 'plain if' macro):

    def value = if(some_condition) { value1 } else { value2 };

    I'll agree with you that I found 'when' to be mildly annoying for the first week or so of using Nemerle, but I gradually came to accept it. Nemerle's macro capabilities are absolutely fantastic though: I just managed to implement the C++ ternary operator as a macro, which means now I can write:

    def value = some_condition ? value1 : value2;

    Which I personally think is a more streamlined way of writing the if statement above. I only needed to get about halfway through the "Nemerle macros extended course" (https://github.com/rsdn/nemerle/wiki/Macros---extended-course.-Part-2 ) to figure out how to do that, it's definitely worth a read: I could probably have saved myself a few hours of headscratching if I'd started from there in the first place.

  2. 2015-05-21 09:06

    >it's the same in Haskell and Standard ML

    Interesting, I didn't know that.

    >It might be interesting to see whether this kind of statement will still compile in the presence of your 'plain if' macro: def value = if(some_condition) { value1 } else { value2 };

    As per plainIf's "syntax()" condition, the plainIf macro only kicks in when there's an "if" that lacks an "else". So that statement compiles and works as usual (and I just ran it through the compiler to double-check).

    But your comment did make me wonder about this:

    def value = if(some_condition) { value1 };

    Turns out (also verified by the actual compiler), since plainIf is implemented as a "when" statement, this statement behaves identical to using "when" as an expression: The whole expression evaluates to a type of "void" and "value" ends up statically typed as "void".

Leave a comment

Captcha