I have discussed about this topic once in past, but in the meantime I have seen
this is a quite common problem, so I think it doesn't harm to touch this topic
again.
This is a direct D translation of the original C or C++ code:
// version #1
double foo;
if (abs(e.x - v.x) > double.min)
foo = (v.y - e.y) / (v.x - e.x);
else
foo = double.max;
This version is clear, easy to understand, efficient, and it's not bug-prone.
Some coding standards require those to use full braces there. But it has some
problems too:
- It's not DRY, "foo" is repeated three times;
- You can't use "auto" there, so if the type of e.x changes, you have to change
the foo type manually (double.min is replaceable with typeof(e.x).min).
- It's not short code.
- And foo can't be const or immutable, I don't like this.
In this specific example one of the two branches of the if contains just a
constant, so you are allowed to write:
// version #2
double foo = double.max;
if (abs(e.x - v.x) > double.min)
foo = (v.y - e.y) / (v.x - e.x);
But generally you can't do that because the then-else branches of the if are
meant to be computed lazily, only one of them.
To turn foo constant, you are free to use a temporary mutable variable, but
this makes the local namespace even more dirty:
// version #3
double _blue;
if (abs(e.x - v.x) > double.min)
foo = (v.y - e.y) / (v.x - e.x);
else
foo = double.max;
immutable foo = _blue;
In D you are allowed to create a function and call it in place:
// version #4
const foo = {
if (abs(e.x - v.x) > double.min)
return (v.y - e.y) / (v.x - e.x);
else
return double.max;
}();
Version #4 has some downsides:
- The return type of this delegate is inferenced. This means that both branches
of the if must return the same type. Currently in D this disallows some
possibilities (I hope this will be fixed), in some cases you have to cast the
empty result to the same type of the other if branch;
- The code is even longer and I don't like its look a lot (in JavaScript it's
fine);
- It's not certain that every D compiler will always inline that delegate. This
risks lower performance;
- That delegate uses values defined outside it, so it can't be pure, so the
function that contains such code can't be pure. You solve this problem making
the code more complex, passing the needed local variables as arguments to the
delegate, but this is not handy.
Version #5 is an acceptable solution, it is compact and it defines just one
variable, that is constant, but the C conditional expression is bug-prone and
it's a bit tricky (it's a common source of various bugs), I don't like it. This
code is less maintainable (if you want to add something you sometimes need to
convert it again into a normal if).
// version #5
const foo = (abs(e.x - v.x) > double.min) ?
((v.y - e.y) / (v.x - e.x)) :
double.max;
In my precedent post about this topic I have discussed "nested constness" and
another partially silly idea. Recently I have seen a language that suggests me
this:
// version #6
const foo;
if (abs(e.x - v.x) > double.min)
foo = (v.y - e.y) / (v.x - e.x);
else
foo = double.max;
The compiler makes sure all paths assign values with the same type to foo (as
in the case of the two returns inside the delegate, that must be of the same
type). But if you introduce goto instructions version #6 looks fragile. If the
assign is far away from the definition the code looks not so nice any more, so
this feature is meant for short-range initializations only.
Bye,
bearophile