October 05, 2017
On Wednesday, 4 October 2017 at 17:56:16 UTC, Walter Bright wrote:
> Please present an example.

Let's say you're importing a C library which defines random function generators. They may be more random than Phobos rngs, they might be crytpo secure or whatever so you want to use them. It could be like:

module crng;

extern(c) struct Crng
{   int seedA;
    int seedB;
    ubyte[] restOfTheData;
}

extern(c) int current(Crng*);
extern(c) void toNextValue(Crng*);
extern(c) Crng initByTime(int unixTime);
//...

But you also want a probos-style range interface for them. You have to wrap the c struct into a new struct type:

struct Crng
{   crng.Crng _impl;
    alias _impl this;
    auto front(){return current(*_impl);}
    void popFront(){toNextValue(*_impl);}
    //...
}

and you have to rewrite many wrappers for Crng functions despite the alias this because they either require return Crng or gequire a pointer to one. This needs to be defined manually for example:

Crng initByTime(int time){return Crng(crng.initByTime(time))};

With ADL it would be enough to extend the original struct with range primitives:

auto front(Crng range){return current(*range);}
void popFront(ref Crng range){toNextValue(*range);}
//...

With current semantics the latter example will work only with locally defined range function templates. Not with Phobos ones, because they cannot see the extension functions.

Note, I do not have a D compiler in where I posted these from so they're not checked for compilation errors.
October 05, 2017
On Wednesday, 4 October 2017 at 17:56:16 UTC, Walter Bright wrote:
> Please present an example.

Let's say you're importing a C library which defines random function generators. They may be more random than Phobos rngs, they might be crytpo secure or whatever so you want to use them. It could be like:

module crng;

extern(c) struct Crng
{   int seedA;
    int seedB;
    ubyte[] restOfTheData;
}

extern(c) int current(Crng*);
extern(c) void toNextValue(Crng*);
extern(c) Crng initByTime(int unixTime);
//...

But you also want a phobos-style range interface for them. You have to wrap the c struct into a new struct type:

struct Crng
{   crng.Crng _impl;
    alias _impl this;
    auto front(){return current(*_impl);}
    void popFront(){toNextValue(*_impl);}
    //...
}

and you have to rewrite many wrappers for Crng functions despite the alias this because they either require return Crng or gequire a pointer to one. This needs to be defined manually for example:

Crng initByTime(int time){return Crng(crng.initByTime(time))};

With ADL it would be enough to extend the original struct with range primitives:

auto front(Crng range){return current(*range);}
void popFront(ref Crng range){toNextValue(*range);}
//...

With current semantics the latter example will work only with locally defined range function templates. Not with Phobos ones, because they cannot see the extension functions.

Note, I do not have a D compiler in where I posted these from so they're not checked for compilation errors.
October 05, 2017
On Thursday, 5 October 2017 at 08:27:14 UTC, Dukc wrote:
> and you have to rewrite many wrappers for Crng functions despite the alias this because they either require return Crng or gequire a pointer to one.

Of course, If we could find a way to automate this universally, there would be much less if any need for ADL.
October 05, 2017
On 03.10.2017 21:25, Walter Bright wrote:
> On 10/2/2017 4:15 AM, Timon Gehr wrote:
>> On 30.09.2017 23:45, Walter Bright wrote:
>>> ...
>>> D has other ways of doing what ADL does,
>>
>> What are those ways? I'm aware of two basic strategies, both suboptimal:
>>
>> - Every module imports all other modules.
>> - Proliferation of wrapper types.
> 
> https://dlang.org/spec/operatoroverloading.html#binary
> 
> C++ does not have this notion.
> 
> 
>> It's not per se related to operator overloading:
> 
> ADL was specifically intended to address operator overloading.
> ...

It's easy to explain why: In C++, operators are the _only_ functions that have UFCS.

This is in stark contrast to D, where all functions _but_ operators have UFCS.

The proposal was allow UFCS also for overloaded operators.

Hence, this discussion is about UFCS. These are not really operator overloading issues.

> 
>> ---
>> module a;
>> import std.range: isInputRange;
>> auto sum(R)(R r)if(isInputRange!R){
>>      typeof(r.front) result;
>>      for(auto t=r.save;!t.empty;t.popFront())
>>          result+=t.front;
>>      return result;
>> }
>> ---
>>
>> ---
>> module b;
>> import a;
>> import std.range;
>>
>> void main(){
>>      int[] a = [1,2,3,4];
>>      import std.stdio: writeln;
>>      writeln(a.front); // ok
>>      writeln(sum(a)); // error, the type is an input range, yet has no front
>> }
>> ---
>>
>> I.e., the operations that are supported on the type differ depending on the module that it is accessed from. If there are multiple definitions of the same name, different modules might not agree which one is being referred to. (This is particularly likely for overloaded operators, as the set of names is finite and small. This is what Manu means when he says it can lead to nasty surprises. This is very plausible, but I don't have a good example.)
> 
> This gets into the anti-hijacking support D has (and C++ does not). If multiple modules with declarations of `writeln` are in scope, the compiler attempts a match with `writeln` in each module. If there is a match with more than one module, an error is issued. The user will then have to specify which one he wants.
> ...

UFCS allows hijacking. For an example, see:
https://github.com/tgehr/d-compiler/pull/1#discussion-diff-89697186L85

Commenting out 'private' caused a stack overflow.

> This is specifically designed to prevent nasty surprises. C++ has a big problem with ADL in that overloading is not encapsulated and any #included header can inadvertently add more overloads that may be entirely unrelated. Any .h can crack open any namespace and insert more overloads into it. It's completely unhygienic and uncontrollable.
> 
> As for the specific example you gave, I get:
> 
> a.d(3): Error: no property 'front' for type 'int[]'
> a.d(4): Error: no property 'save' for type 'int[]'
> b.d(8): Error: template instance a.sum!(int[]) error instantiating

(There is a comment in the code noting that it will not compile.)

The intention of the code was to demonstrate that a type can pass isInputRange in the same module in which it does not support front. This is an example of surprising name lookup behavior.

Of course there is also the opposite problem. You can have a type that supports all range primitives via UFCS but does not pass isInputRange, because Phobos does not import the module where the primitives are defined. (This particular case is sometimes solved by ADL, sometimes not.)

---

struct Iota{ private int a,b; }
auto iota(int a,int b){ return Iota(a,b); }

@property int front(Iota i){ return i.a; }
@property bool empty(Iota i){ return i.a>=i.b; }
void popFront(ref Iota i){ ++i.a; }

void main(){
    import std.stdio;
    for(auto r=iota(0,10);!r.empty;r.popFront){ // ok
        writeln(r.front);
    }
    import std.algorithm;
    iota(0,10).each!writeln; // error
    foreach(i;iota(0,10)) writeln(i); // error
}

---

If I copy-paste portions of std.range into the main module of the above program instead of importing, I will be able to use my custom type with those Phobos ranges.
October 05, 2017
On 10/4/2017 11:44 PM, Jacob Carlborg wrote:
> On 2017-10-05 00:59, Walter Bright wrote:
> 
>> An example would be appreciated. Timon's example requires guesswork as to what he intended, because it does not compile in ways unrelated to his point.
> 
> It's supposed to not compile, because D doesn't have ADL.

If that is what Timon intended, ok. It wasn't clear to me. But I'm left wondering what the issue variously described as suboptimal, nasty, no solution, etc., is.

The worst I can see is if you didn't import the relevant modules, you'll get a compiler error.
October 05, 2017
Seems to me just add:

  public import crng;

to the module defining struct Crng.
October 05, 2017
On 05.10.2017 11:21, Walter Bright wrote:
> Seems to me just add:
> 
>    public import crng;
> 
> to the module defining struct Crng.

(That module is crng itself.)

The "fix" is to add

    public import crng;

to _std.range_.
October 05, 2017
On Thursday, 5 October 2017 at 09:26:44 UTC, Timon Gehr wrote:
> (That module is crng itself.)

Depends on if he meant the extern (c) Crng or the range-implementing Crng. But would still not work, unless i have misunderstood something.

> The "fix" is to add
>
>     public import crng;
>
> to _std.range_.

Yes, would work. I don't think it needs explanation why it's, as you said, more like a "fix" than a fix.

October 05, 2017
On Thursday, 5 October 2017 at 09:39:58 UTC, Dukc wrote:
> On Thursday, 5 October 2017 at 09:26:44 UTC, Timon Gehr wrote:
>> The "fix" is to add
>>
>>     public import crng;
>>
>> to _std.range_.
>
> Yes, would work. I don't think it needs explanation why it's, as you said, more like a "fix" than a fix.

Correction: Wouldn't quite do it, std.range would need to import the extension functions to crng, not it's C api. And it needn't to be a public import, if you also import it to it's child modules it happens to import. Otherwise correct.

October 05, 2017
On 10/5/2017 2:13 AM, Timon Gehr wrote:
> It's easy to explain why: In C++, operators are the _only_ functions that have UFCS.
> 
> This is in stark contrast to D, where all functions _but_ operators have UFCS.
> 
> The proposal was allow UFCS also for overloaded operators.
> 
> Hence, this discussion is about UFCS. These are not really operator overloading issues.

Ok, but I'm not sure what the proposal was.


> UFCS allows hijacking. For an example, see:
> https://github.com/tgehr/d-compiler/pull/1#discussion-diff-89697186L85

That may be a bug in the compiler. Can you produce a small test case? I know that some of the UFCS code was written without regard to hijacking.


> (There is a comment in the code noting that it will not compile.)

Right, but I wasn't sure what errors you expected to see. I hate to assume with these sorts of things, as people often post examples with errors they didn't intend. This is why I ask for examples, and it's nice to also point out the errors.


> The intention of the code was to demonstrate that a type can pass isInputRange in the same module in which it does not support front. This is an example of surprising name lookup behavior.

I submit it is surprising only if you're used to ADL :-)

ADL in C++ makes lookups bafflingly complex, and I doubt many C++ people actually understand it. It's like C++ overload resolution, nobody understands it, not even me (and I implemented it to the letter of the spec!). Some will argue it "just works", and no understanding is necessary, and I suppose that's fine until one gets an incomprehensible compiler error.

D's name lookup rules were quite simple, and deliberately set up that way. Unfortunately, most people thought they were unintuitive. It turns out that simple algorithms are not intuitive, and we now have a fairly complex lookup system. Martin and I implemented it, and probably neither of us completely understands it. I find it regrettable that things have gotten to that state.


> Of course there is also the opposite problem. You can have a type that supports all range primitives via UFCS but does not pass isInputRange, because Phobos does not import the module where the primitives are defined. (This particular case is sometimes solved by ADL, sometimes not.)
> 
> ---
> 
> struct Iota{ private int a,b; }
> auto iota(int a,int b){ return Iota(a,b); }
> 
> @property int front(Iota i){ return i.a; }
> @property bool empty(Iota i){ return i.a>=i.b; }
> void popFront(ref Iota i){ ++i.a; }
> 
> void main(){
>      import std.stdio;
>      for(auto r=iota(0,10);!r.empty;r.popFront){ // ok
>          writeln(r.front);
>      }
>      import std.algorithm;
>      iota(0,10).each!writeln; // error
>      foreach(i;iota(0,10)) writeln(i); // error
> }
> 
> ---
> 
> If I copy-paste portions of std.range into the main module of the above program instead of importing, I will be able to use my custom type with those Phobos ranges.

I understand this one, as it helpfully says the expected error :-) thank you.

I suggest for this case writing a wrapper type for Iota, with front/empty/popFront as members of that wrapper, then it should be good to go. It's hardly any more work than writing the free functions. Doing this kind of wrapper doesn't work for operator overloading, which brings us back to ADL is for operator overloading.