"hijackable"/"customizable" keyword for solving the "customized algorithm" issue?

Ary Borenszweig via Digitalmars-d digitalmars-d at puremagic.com
Thu May 15 04:01:52 PDT 2014


On 5/15/14, 7:54 AM, Ary Borenszweig wrote:
> On 5/14/14, 3:05 PM, monarch_dodra wrote:
>> A little while ago, Steven Schveighoffer started a thread called "D UFCS
>> anti-pattern". The conversation started in regards to "how can a generic
>> algorithm be replaced by a custom type". In particular (in my original
>> use case) "retro"
>>
>> The issue just came up again in ".learn" for ".find". It comes up
>> regularly with ".put".
>>
>> To put it simply, the problem is "given the function std.foo, how can I
>> implement myObject.foo, and make sure that that is called when we call
>> foo?"
>
> That's the big problem with UFCS.
>
> If you were forced to always use UFCS then this wouldn't be a problem:
> if the type at question provides a more specific implementation *that
> matches the arguments' types*, the compiler takes it. Otherwise, it
> checks for free functions that have that type as a first argument and
> the rest of the arguments.
>
> But once you have an option to invoke it as a free function you loose.
> The "U" of UFCS means "uniform", so in my opinion when you do:
>
> foo(x, y);
>
> then the compiler must first check if the type of "x" defines "foo(y)".
> If so, then it invokes that specialized version. If not, it must check
> if "foo(x, y)" exists somewhere else.
>
> If you do:
>
> x.foo(y);
>
> the compiler would do exactly the same.
>
> This is uniformity: either way I invoke a function, the compiler's logic
> is the same.
>
> But if you can write a "same thing" in two different ways but behave
> differently, that's looking for trouble.
>
> If you don't want the compiler to convert foo(x, y) to x.foo(y), then
> use a qualified name:
>
> bar.baz.foo(x, y);

Of course, the "problem" with this is that when you read this code:

foo(x, y);

to understand it, you must first check if there's a "foo" definition 
somewhere in your imported modules that matches. Otherwise, check if x's 
type defines it.

The same is true for this:

x.foo(y);

To understand it, you must check if x's type defines a "foo(y)". If so, 
it's taken. Otherwise, check which of the functions in the imported 
modules matches "foo(x, y)".

This makes it really hard to reason about code: everytime you see 
"foo(x, y)" you must remember which of the modules had that function 
"foo", or if x defines "foo".

In OOP-like land, "foo(x, y)" always means: check the function "foo(x, 
y)". And "x.foo(y)" always means: check the member "foo" of x's type. No 
confusion at all for the reader. But "foo(x, y)" is almost never used 
(global functions), so you never have to remember where methods are 
located by heart. You just go to that type's code and find the method there.


More information about the Digitalmars-d mailing list