On 13.08.2010 19:17, Steven Schveighoffer wrote:
On Fri, 13 Aug 2010 13:01:47 -0400, simendsjo
<simen.end...@pandavre.com> wrote:

While reading std.range, I though that a ducktyping design without
language/library support can be quite fragile.

Consider the following example:

import std.stdio;

struct S
{
void shittyNameThatProbablyGetsRefactored() { };
}

void process(T)(T s)
{
static if( __traits(hasMember, T,
"shittyNameThatProbablyGetsRefactored"))
{
writeln("normal processing");
}
else
{
writeln("Start nuclear war!");
}
}


void main()
{
S s;
process(s);
}


If you rename S's method, process() does something completely
different without a compile time error. By using interfaces this is
avoided as the rename would break the interface.

Is there any idoms you can use to avoid stuff like this? Relying on
documentation doesn't seem like a good solution.

You have somewhat missed the point of duck typing. It would look more
like this:

void process(T)(T s)
{
s.shittyNameThatProbabyGetsRefactored();
}


Basically, the point is, you compile *expecting* that you can call the
function, and then when the type doesn't have the function, it simply
fails.

Of course, the error you get is not what you want, because to the
compiler, it's not the call of the function that is the error, it's the
compiling of the function that is the error.

To remedy this, you use template constraints:

void process(T)(T s) if(__traits(hasMember, T,
"shittyNameThatProbabyGetsRefactored")
{
...
}

And then the compiler won't even try to compile the function, it just
fails at the call site.

-Steve

Ok, point taken. But take a look at
void put(R, E)(ref R r, E e)
in std.range for instance. This function uses a member put if it exists, then front/popfront if it's an input range or opCall as a last instance.

It's easy to imagine such a design for other types where suddenly the program behaves differently because of a rename.

Is "put" a bad design?

Reply via email to