Opinions: The Best and Worst of D (for a lecture/talk I intend to give)

H. S. Teoh via Digitalmars-d-learn digitalmars-d-learn at puremagic.com
Mon Jul 7 22:50:09 PDT 2014


On Mon, Jul 07, 2014 at 11:47:25PM +0000, Aerolite via Digitalmars-d-learn wrote:
[...]
> So, if you would be so kind, give me a bullet list of the aspects of D
> you believe to be good, awesome, bad, and/or ugly. If you have the
> time, some code examples wouldn't go amiss either! Try not to go
> in-depth to weird edge cases - remain general, yet informative. E.g. I
> consider D's string mixins to be in the 'awesome' category, but its
> reliance on the GC for large segments of the standard library to be in
> the 'ugly' category.
[...]

String mixins are a controversial feature. Many (including myself) think
they are definitely awesome, but that power also comes with the price of
being harder to maintain, and difficult to integrate with IDE features
(though the latter doesn't matter to me 'cos I don't use IDEs). Because
of that, some others think they are a misfeature, and have proposed to
remove them. But I don't think it's going away anytime soon, since
several key features depend on it.

Perhaps I may bring up a representative use case: operator overloading.
In C++, if you implement a custom number type, for example, you have to
overload operator+, operator*, operator/, operator-. And then you
realize you left out operator+=, operator*=, operator/=, ... and then
you need operator<, operator>, operator<=, operator>=, ad nauseum. In D?

	// This covers +, -, *, /, +=, -=, *=, /=, etc..
	auto opBinary(string op)(NumType n)
	{
		return NumType(mixin(this.val ~ op ~ n.val));
	}

	// And this covers all the comparison operators:
	int opCmp(NumType n) {
		return ... /* implementation here */
	}

Without string mixins, you'd have to copy-n-paste a ton of boilerplate
to get the necessary operator overloadings.

//

About the GC, my opinion is that anti-GC sentiment is largely unfounded.
The GC greatly simplifies certain tasks (string manipulation, for one
thing, and returning recursive data structures like trees). There *are*
cases where the GC can cause trouble, like in game engines, esp. given
that the GC implementation in D leaves a lot of room for improvement,
but a lot of C/C++ programmers have knee-jerk reactions about GCs for no
other reason than unfamiliarity breeding contempt (I speak for myself,
since I come from a strong C/C++ background, and had the same reaction),
than any truly objective measurements. I'd say a large percentage of
applications don't need to manually micro-manage memory, and having a GC
eliminates an entire class of bugs and greatly improves productivity.

//

But anyway, not to dwell on a contentious issue, let's move on to
something oft overlooked (and sometimes even maligned): unittests. I
could sing praises of D's built-in unittest blocks all day. When I was
young and foolish, I prided myself on being the macho C/C++ programmer
whose code will work the first time after I write it. Who needs tests?
They are for the weak-minded, who cannot grasp their code in every last
detail of their programs in their head with absolute clarity. Real
Programmers can write code in their dreams, and it will work the first
time they run it.

Except that they don't. :P  When I first started learning D, I tried my
best to ignore unittests, telling myself that it's for weaker
programmers, but they just kept sitting their, right in the language,
and staring at me, all cute-like, until I was shamed into actually
writing a few of them just to quiet that nagging voice within. And lo
and behold, I started finding bugs in my "perfect" code -- corner cases
I missed (even though I painstakingly took care of all the *other*
corner cases), subtle typos that compiled but gave the wrong answers,
blatant errors that I missed due to obsession over tiny details, etc..
To my chagrin, I discovered that I was *not* the macho perfect
programmer that I imagined, and that these unittests for the weak were
singlehandedly improving the quality of my code by leaps and bounds.

Sometimes, the mere act of writing unittests would bring my attention to
a corner case that I'd missed, and I'd fix the code, long before my past
self would've noticed it (by finding it as a runtime bug much later).
When I revised the code later, I suddenly could have peace of mind that
if the unittests didn't break, then in all likelihood the new code is
still correct (or mostly so). Regressions were instantly noticed, rather
than weeks or months down the road when I suddenly needed a corner case
that I knew was previously working but had since been broken by a later
revision.

Unittests in the language == total win.  External unittest frameworks
may be more powerful, more flexible, etc., but I'd never use them, 'cos
they are too troublesome. I have to switch out of coding mode, open a
new file, and possibly shift gears to a different language, write the
test, then switch back, and later on it becomes too trouble to keep
switching back and forth (and maintaining the unittests) so I'd just not
bother running them, and when I do run them, I'd regard them with
loathing that they failed Yet Again!, and so I'd hesitate adding any
more tests. D's built-in unittests? Total win. You can call other
functions, submodules, etc., from the unittest, which makes it much,
much easier to setup scaffolding for testing. Trying to do that in a
dedicated testing language is an exercise in pain tolerance.

//

And it's getting late, and this email is getting way too long, but I
can't help bringing up metaprogramming. That's probably the one thing
that singlehandedly converted me to D.  If you got your impression of
metaprogramming from that nasty rats'-nest known as C++ templates, then
you have my deepest sympathies, but that is *not* representative of what
metaprogramming can be. D's templates, which are perhaps better regarded
as compile-time parameters, plus compile-time introspection and CTFE,
launch D into new heights of expressiveness and compile-time power.
Regretfully I need to go soon, but I would point out Dmitry Olshansky's
std.regex.ctRegex as an example of metaprogramming that allows you to
write regexes in comfortable, natural syntax (unlike C++'s monstrous
*cough*Xpressive*ahem*), yet with full compile-time optimization that
outperforms basically every other regex library out there. No small
feat, one has to admit!

//

Alright. The warts. The bad. There are a few, unfortunately.

- The current AA implementation leaves a lot to be desired... It has
  been improving, and hope is bright, but it's still rather messy, and
  interacts poorly with other built-in features, like const / immutable,
  dtors, @disabled ctors, etc.. Use string keys, and you're mostly OK.
  But once const starts getting in there, or dtors, and things quickly
  get ugly, real fast. Strange corner cases, weird bugs, and other
  pathological implementation holes start showing up.

- Dtors, in general, are prone to nastiness. (One could argue this is
  where having a GC stinks.) Basic usage, for the most part, is OK.
  Start mixing it with, say, closures, containers, @disabled, etc., and
  you quickly run into a minefield of pitfalls that will, on the
  positive side, *quickly* educate you on the arcane deep aspects of D,
  but on the negative side, will also have you pulling out all your hair
  in short order.

- Using array literals with enums (i.e., as a manifest constant) is a
  Very Nasty Very Bad thing to do. It will silently allocate a new array
  every single time you reference that enum, thereby destroying the
  performance benefits of a compiled language very quickly. Having this
  as default behaviour is a big design mistake IMO, and doesn't jive
  with the rest of the language where the default behaviour is the sane
  behaviour, and to get weird behaviour you have to explicitly ask for
  it.

- @safe still isn't quite usable yet. It's there, and many things
  support it, but there are still fundamental features (including
  library features) that aren't @safe-compatible yet, which makes it
  unusable for me. Not to mention there are currently holes in the @safe
  system.

- Contracts (DbC) are a good idea, but the current implementation is
  disappointing. There are still some glaring holes in it that may or
  may not be easy to fix. As things stand, it still has a ways to go.

- ... OK, there's a lot more stuff I want to write but it's late and I
  have to go now, so maybe I'll chime in again later. Maybe.


T

-- 
Food and laptops don't mix.


More information about the Digitalmars-d-learn mailing list