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

Reply via email to