Thread overview | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
May 14, 2014 "hijackable"/"customizable" keyword for solving the "customized algorithm" issue? | ||||
---|---|---|---|---|
| ||||
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?" There were two options: 1. Make std.foo test "hasMember!(T, "foo")". This would hardly scale though, as applying it to *every* algorithm is not acceptable 2. For *all* calls to be UFCS style, and ignore the "anti-pattern issue". Even then, it means you give no guarantees as to *what* is called, depending on how the user writes the code. So not acceptable I just had a crazy idea. "hijackable" keyword (yeah... another keyword): Given a function: "Ret foo(T input, Args... args) @hijackable" Then, when the compiler sees: "foo(input, args);" It will always forward directly to T.foo if T.foo exists, bypassing std.foo entirely. I think it is a clean and generic way to allow ALL algorithms in D much greater customize-ability. I didn't put *much* more thought than that to it yet, so I may have missed something obvious? In particular, "hijack" might not be the correct word? "@customizable" ? Please provide feedback/destruction ? |
May 14, 2014 Re: "hijackable"/"customizable" keyword for solving the "customized algorithm" issue? | ||||
---|---|---|---|---|
| ||||
Posted in reply to monarch_dodra | I'm going to get into an opinion on member functions and overrides here, and it's kind of "anti-classes," so you can choose to ignore this post if you don't want to hear about that. I think this is a bad idea. One of the things that really appeals to me about algorithms presented through functions is that I can predict the behaviour of them. It will do something generically on a category of types. I think any need to change the behaviour of an algorithm either means that the algorithm was written incorrectly, or really you just have a different algorithm and you should probably use another name for it. One of the things I've really lost faith in over the years is method overrides, becuase of how confusing it has made code I have had to read. These days I prefer generic functions that give me one or maybe a couple of places to look when something goes wrong. |
May 14, 2014 Re: "hijackable"/"customizable" keyword for solving the "customized algorithm" issue? | ||||
---|---|---|---|---|
| ||||
Posted in reply to monarch_dodra | On Wednesday, 14 May 2014 at 18:05:44 UTC, 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?"
>
> There were two options:
> 1. Make std.foo test "hasMember!(T, "foo")". This would hardly scale though, as applying it to *every* algorithm is not acceptable
> 2. For *all* calls to be UFCS style, and ignore the "anti-pattern issue". Even then, it means you give no guarantees as to *what* is called, depending on how the user writes the code. So not acceptable
>
> I just had a crazy idea. "hijackable" keyword (yeah... another keyword):
>
> Given a function:
> "Ret foo(T input, Args... args) @hijackable"
>
> Then, when the compiler sees:
> "foo(input, args);"
>
> It will always forward directly to T.foo if T.foo exists, bypassing std.foo entirely.
>
> I think it is a clean and generic way to allow ALL algorithms in D much greater customize-ability.
>
> I didn't put *much* more thought than that to it yet, so I may have missed something obvious? In particular, "hijack" might not be the correct word? "@customizable" ?
>
> Please provide feedback/destruction ?
It's a good idea, but yet another function annotation is pretty much a no-go. How bad an idea is it to *always* defer to a member function if the object/struct in question has such a function defined? I thought that was the case already... I suppose it will cause untold amounts of code breakage. Very unfortunate.
|
May 15, 2014 Re: "hijackable"/"customizable" keyword for solving the "customized algorithm" issue? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Meta | On Wednesday, 14 May 2014 at 23:57:24 UTC, Meta wrote:
> It's a good idea, but yet another function annotation is pretty much a no-go. How bad an idea is it to *always* defer to a member function if the object/struct in question has such a function defined? I thought that was the case already... I suppose it will cause untold amounts of code breakage. Very unfortunate.
UFCS only apply to the method call style, not the the function call style, so it's not a matter of priority here - `foo(myObject)` will not call the `foo.myObject()` method even if there is no `foo` function(in that case it'll just fail).
Having the function call style always defer to a member function is a really bad idea - and not just because of code breakage. It'll make it impossible to call a function on an object that has a method of the same name unless you use an alias or a function variable to copy the function - but then you have to make sure the alias\variable name is also not taken!
This will be a disaster for anyone who wants to build or use a D library that uses templates. Well, how common can that case be?
|
May 15, 2014 Re: "hijackable"/"customizable" keyword for solving the "customized algorithm" issue? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Idan Arye | On Thursday, 15 May 2014 at 01:49:17 UTC, Idan Arye wrote:
> UFCS only apply to the method call style, not the the function call style, so it's not a matter of priority here - `foo(myObject)` will not call the `foo.myObject()` method even if there is no `foo` function(in that case it'll just fail).
>
> Having the function call style always defer to a member function is a really bad idea - and not just because of code breakage. It'll make it impossible to call a function on an object that has a method of the same name unless you use an alias or a function variable to copy the function - but then you have to make sure the alias\variable name is also not taken!
>
> This will be a disaster for anyone who wants to build or use a D library that uses templates. Well, how common can that case be?
That's exactly what the proposed @hijackable annotation on a function would do, if I understand it correctly.
|
May 15, 2014 Re: "hijackable"/"customizable" keyword for solving the "customized algorithm" issue? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Meta | On Thursday, 15 May 2014 at 02:07:40 UTC, Meta wrote:
> On Thursday, 15 May 2014 at 01:49:17 UTC, Idan Arye wrote:
>> UFCS only apply to the method call style, not the the function call style, so it's not a matter of priority here - `foo(myObject)` will not call the `foo.myObject()` method even if there is no `foo` function(in that case it'll just fail).
>>
>> Having the function call style always defer to a member function is a really bad idea - and not just because of code breakage. It'll make it impossible to call a function on an object that has a method of the same name unless you use an alias or a function variable to copy the function - but then you have to make sure the alias\variable name is also not taken!
>>
>> This will be a disaster for anyone who wants to build or use a D library that uses templates. Well, how common can that case be?
>
> That's exactly what the proposed @hijackable annotation on a function would do, if I understand it correctly.
Right. The idea though is not to "customize the behavior" of the algorithm, but rather "provide a more efficient implementation". It shouldn't really break any existing code.
In any case, it should not cause any more than the breakage that using UFCS *already* causes... this would just (opt-in by the implementation) make sure you get the same behavior with/without UFCS.
FYI, there are already a few functions in phobos that delegate to member functions "if they exist", and do something generic if they don't. "Take" and "put" are some of the more trivial examples. "move" will also delegate to "proxyMove". At one point, there were also talks of doing the same for popFrontN too.
The issue though is that it's all very hackish, and it makes the implementation jump through hoops to achieve said result, and it only works if the implementation pre-emptivelly thought about doing so.
|
May 15, 2014 Re: "hijackable"/"customizable" keyword for solving the "customized algorithm" issue? | ||||
---|---|---|---|---|
| ||||
Posted in reply to monarch_dodra | 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);
|
May 15, 2014 Re: "hijackable"/"customizable" keyword for solving the "customized algorithm" issue? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Ary Borenszweig | 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.
|
May 15, 2014 Re: "hijackable"/"customizable" keyword for solving the "customized algorithm" issue? | ||||
---|---|---|---|---|
| ||||
Posted in reply to monarch_dodra | On Thu, 15 May 2014 02:05:08 -0400, monarch_dodra <monarchdodra@gmail.com> wrote:
> "move" will also delegate to "proxyMove".
This is the correct solution IMO. The principle of least surprise should dictate that a.foo should always mean the same thing. All that is required to enforce this is to make the "hook" function have a different name.
I think the two mechanisms of overriding default behavior:
1. Use a hook, to define the basic minimum functionality.
2. Implement the same-named method, but you must implement ALL functionality (possibly deferring to the global function if necessary).
-Steve
|
May 15, 2014 Re: "hijackable"/"customizable" keyword for solving the "customized algorithm" issue? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Meta | On Thursday, 15 May 2014 at 02:07:40 UTC, Meta wrote:
> On Thursday, 15 May 2014 at 01:49:17 UTC, Idan Arye wrote:
>> UFCS only apply to the method call style, not the the function call style, so it's not a matter of priority here - `foo(myObject)` will not call the `foo.myObject()` method even if there is no `foo` function(in that case it'll just fail).
>>
>> Having the function call style always defer to a member function is a really bad idea - and not just because of code breakage. It'll make it impossible to call a function on an object that has a method of the same name unless you use an alias or a function variable to copy the function - but then you have to make sure the alias\variable name is also not taken!
>>
>> This will be a disaster for anyone who wants to build or use a D library that uses templates. Well, how common can that case be?
>
> That's exactly what the proposed @hijackable annotation on a function would do, if I understand it correctly.
Yes, but there is quite a difference between applying this to specific functions where this behavior is desirable and applying this to everything.
|
Copyright © 1999-2021 by the D Language Foundation