December 11, 2018

Improvements I’d like to see in D

D, as any language that stands on the shoulder of giants, was conceived to not repeat the errors of the past, and I think it’s done an admirable job at that. However, and perfectly predictably, it made a few of its own. Sometimes, similar to the ones it avoided! In my opinion, some of them are:

No UFCS chain for templates.

UFCS is a great feature and was under discussion to be added to C++, but as of the C++17 standard it hasn’t yet. It’s syntatic sugar for treating a free function as a member function if the first parameter is of that type, i.e. allows one to write obj.func(3) instead of func(obj, 3). Why is that important? Algorithm chains. Consider:

range.map!fun.filter!gun.join

Instead of:

join(filter!gun(map!fun(range)));

It’s much more readable and flows more naturally. This is clearly a win, and yet I’m talking about it in a blog post about D’s mistakes. Why? Because templates have no such thing. There are compile-time “type-level” versions of both map and filter in D’s standard library called staticMap and Filter (the names could be more consistent). But they don’t chain:

alias memberNames = AliasSeq!(__traits(allMembers, T));
alias Member(string name) = Alias!(__traits(getMember, T, name));
alias members = staticMap!(Member, memberNames);
alias memberFunctions = Filter!(isSomeFunction, members);

One has to name all of these intermediate steps even though none of them deserve to be named, just to avoid writing a russian doll parenthesis unreadable nightmare. Imagine if instead it were:

alias memberFunctions = __traits(allMembers, T)
    .staticMap!Member
    .Filter!(isSomeFunction);

One can dream. Which leads me to:

No template lambdas.

In the hypothetical template UFCS chain above, I wrote staticMap!Member, where the Member definition is as in the example before it. However: why do I need to name it either? In “regular” code we can use lambdas to avoid naming functions. Why can’t I do the same thing for templates?

alias memberFunctions = __traits(allMembers, T)
    .staticMap!(name => Alias!(__traits(getMember, T, name)))
    .Filter!isSomeFunction

Eponymous templates

Bear with me: I think the idea behind eponymous templates is great, is just that the execution isn’t, and I’ll explain why by comparing it to something D got right: constructors and destructors. In C++ and Java, these special member functions take the name of the class, which makes refactoring quite a bit annoying. D did away with that by naming them this and ~this, which makes the class name irrelevant. The way to do eponymous templates right is to (obviously renaming the feature) follow D’s own lead here and use either this or This to refer to itself. I’ve lost count of how many times I’ve introduced a bug due to template renaming that was hard to find.

@property getters

What are these for given the optional parentheses for functions with no parameters?

inout

Template this can accomplish the same thing and is more useful anyway.

Returning a reference

It’s practically pointless. Variables can’t be ref, so assigning it to a function that returns ref copies the value (which is probably not what the user expected), so one might as well return a pointer. The exception would be UFCS chains, but before DIP1016 is accepted and implemented, that doesn’t work either.

Wrong defaults

I think there’s a general consensus that @safe, pure and immutable should be default. Which leads me to:

Attribute soup

I’ve lost count now of how many times I’ve had to write @safe @nogc pure nothrow const scope return. Really.