May 15, 2014
On Thursday, 15 May 2014 at 12:16:52 UTC, Steven Schveighoffer wrote:
> 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.

The issue(s) I have with the "hook must have a different name" is:
1. In the algorithm implementation, you must explicitly test for the hook, which, if we want to expand on, would turn all our algorithms into:
void foo(T)(T t)
{
    static if (is(typeof(t.fooHook())))
        return t.fooHook();
    else
        ...
}
2. The "overriden" hook is only useful if you import the corresponding algorithm. So for example, in my 3rd pary library, if my type has "findHook", I'd *have* to import std.algorithm.find for it to be useful. Unless I want to make a direct call to "findHook", which would be ugly...

An example for "2" are range primitives:

> 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

Also: "moveFront"/"moveBack" are other examples of such "globally hijack-able" functions.
May 15, 2014
On Thu, 15 May 2014 13:08:56 -0400, monarch_dodra <monarchdodra@gmail.com> wrote:

> On Thursday, 15 May 2014 at 12:16:52 UTC, Steven Schveighoffer wrote:
>> 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.
>
> The issue(s) I have with the "hook must have a different name" is:
> 1. In the algorithm implementation, you must explicitly test for the hook, which, if we want to expand on, would turn all our algorithms into:
> void foo(T)(T t)
> {
>      static if (is(typeof(t.fooHook())))
>          return t.fooHook();
>      else
>          ...
> }
> 2. The "overriden" hook is only useful if you import the corresponding algorithm. So for example, in my 3rd pary library, if my type has "findHook", I'd *have* to import std.algorithm.find for it to be useful. Unless I want to make a direct call to "findHook", which would be ugly...

Obviously, foo is not a good example for hooking. This problem only exists if the functionality of the module-level function exceeds that of the member. The above does not have any overloads, and basically does one thing.

I did consider the fact that you would have to import the module-level function in order for it to be part of the API, but that's how the system works. It would be nice to say "if you import module x, then a.foo has more functionality, if you don't, it's limited to this one aspect," but I don't think that's possible. It's not a perfect solution, but the only one that makes sense to me.

-Steve
May 16, 2014
On Thursday, 15 May 2014 at 17:08:58 UTC, monarch_dodra wrote:
> On Thursday, 15 May 2014 at 12:16:52 UTC, Steven Schveighoffer wrote:
>> 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.
>
> The issue(s) I have with the "hook must have a different name" is:
> 1. In the algorithm implementation, you must explicitly test for the hook, which, if we want to expand on, would turn all our algorithms into:
> void foo(T)(T t)
> {
>     static if (is(typeof(t.fooHook())))
>         return t.fooHook();
>     else
>         ...
> }
> 2. The "overriden" hook is only useful if you import the corresponding algorithm. So for example, in my 3rd pary library, if my type has "findHook", I'd *have* to import std.algorithm.find for it to be useful. Unless I want to make a direct call to "findHook", which would be ugly...

How about a middle ground?  Have the function names be identical, and decorate the member version with @proxy or @hook, rather than decorating the original definition.  I'd find this to be less surprising.
May 16, 2014
On Wednesday, 14 May 2014 at 18:05:44 UTC, monarch_dodra wrote:
...
> 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.
...

What about stuff like this:
  import some.pkg;
  some.pkg.foo(x);

Does this still call x.foo()?  What if 'foo' is an alias for 'bar', or vice versa?
May 16, 2014
On Friday, 16 May 2014 at 16:50:37 UTC, Yota wrote:
> On Wednesday, 14 May 2014 at 18:05:44 UTC, monarch_dodra wrote:
> ...
>> 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.
> ...
>
> What about stuff like this:
>   import some.pkg;
>   some.pkg.foo(x);
>
> Does this still call x.foo()?

In theory, yes, because "some.pkg" would *be* X.foo, or at the very least, a very thin wrapper. You'd get the very same result with "static if" based implementation, such as "moveFront", where "std.range.moveFront(r)" still just calls "r.moveFront()". Ditto for "take", which just calls opSlice.

> What if 'foo' is an alias for 'bar', or vice versa?

An alias shouldn't change anything I think.
May 17, 2014
On Fri, 16 May 2014 16:45:28 +0000
Yota via Digitalmars-d <digitalmars-d@puremagic.com> wrote:

> On Thursday, 15 May 2014 at 17:08:58 UTC, monarch_dodra wrote:
> > On Thursday, 15 May 2014 at 12:16:52 UTC, Steven Schveighoffer wrote:
> >> 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.
> >
> > The issue(s) I have with the "hook must have a different name"
> > is:
> > 1. In the algorithm implementation, you must explicitly test
> > for the hook, which, if we want to expand on, would turn all
> > our algorithms into:
> > void foo(T)(T t)
> > {
> >     static if (is(typeof(t.fooHook())))
> >         return t.fooHook();
> >     else
> >         ...
> > }
> > 2. The "overriden" hook is only useful if you import the
> > corresponding algorithm. So for example, in my 3rd pary
> > library, if my type has "findHook", I'd *have* to import
> > std.algorithm.find for it to be useful. Unless I want to make a
> > direct call to "findHook", which would be ugly...
>
> How about a middle ground?  Have the function names be identical, and decorate the member version with @proxy or @hook, rather than decorating the original definition.  I'd find this to be less surprising.

That sounds like an interesting idea, but it would require adding the concept to the compiler - though I suppose that that's a downside with monarch_dodra's original proposal as well. Neither can be done in the library itself. That's not necessarily the end of the world, but at this point, I'm very hesitant to support adding another attribute to the language without a really, really good reason.

This particular problem can be solved by Steven's suggestion of using proxy functions with different names, which works within the language as it's currently defined. So, while that might not be an altogether desirable solution, I'm inclined to believe that it's good enough to make it not worth adding additional attributes to the language to solve the problem.

- Jonathan M Davis
1 2
Next ›   Last »