October 05, 2017
On 05.10.2017 12:07, nkm1 wrote:
> Am I missing something? You can already extend the original struct:
> 
> extern(c) struct Crng
> {   int seedA;
>      int seedB;
>      ubyte[] restOfTheData;
> 
>      extern (D) {
>          // or without extern (D)...
>          auto front() { return current(&this); }
>          void popFront() { toNextValue(&this); }
>      }
> }
> 
> What does it matter if you put your extension functions inside the struct or outside of it? (same question to Timon)

For example:

- Methods force the 'this' argument to be references (for structs) or values (for classes).

- Adding functionality to a type in the module where it is defined is not the only use case. Wrapper types can be more cumbersome to use than the original type.
October 05, 2017
On 10/5/2017 4:36 AM, Timon Gehr wrote:
> I forgot to answer to this.

Thanks for the explanation!
October 05, 2017
On 10/5/2017 3:23 AM, Dukc wrote:
> Since when have C struct had array members?

Since the dawn of time :-)

The problems come from when you use a D only type as a function parameter type in a C++ function. Then, the C++ function mangler fails because, well, the C++ community has sadly neglected to add D types to the C++ ABI :-)

When cases like that crop up, I switch it to C mangling, which means no mangling, so no problems! and hopefully overloading isn't involved. (Overloading isn't supported with C functions, because overloading relies on name mangling and C functions are not mangled.)
October 05, 2017
On 10/5/2017 6:13 AM, Steven Schveighoffer wrote:
> On 10/3/17 10:00 PM, Walter Bright wrote:
>> On 10/3/2017 5:24 PM, Steven Schveighoffer wrote:
>>> It also can be cheaper to pass a small struct by value.
>>
>> Should not be a problem if the compiler inlines it.
> 
> That's not always possible.

Right. But then the question becomes how much more complexity do we want to add chasing that last percent of perfection?

For example, right now I'm in my 3rd day of attempting to resolve

  https://issues.dlang.org/show_bug.cgi?id=17635

which is a regression brought about by layers and layers of fixes over time for a seemingly simple issue - implicitly converting a unique return from a pure function into an immutable.

For another example, it took Martin and I months to implement the new import lookup scheme. It turned out to be fairly complicated, and there were many regressions. There are probably still issues lurking in it.

We need to keep the language rules simple enough to understand and simple enough to implement, and there will be compromises with that.
October 05, 2017
On 10/5/2017 4:26 AM, Timon Gehr wrote:
>>> 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?
> 
> struct S{
>      // string foo(){ return "hijacked!"; } // uncomment to hijack
> }
> 
> string foo(S s){ return "not hijacked!"; }
> 
> void main(){
>      S s;
>      import std.stdio;
>      writeln(s.foo());
> }

Thank you:

 https://issues.dlang.org/show_bug.cgi?id=17879
October 05, 2017
On 05.10.2017 21:40, Walter Bright wrote:
>>
> 
> Right. But then the question becomes how much more complexity do we want to add chasing that last percent of perfection?
> 
> For example, right now I'm in my 3rd day of attempting to resolve
> 
> https://issues.dlang.org/show_bug.cgi?id=17635
> 
> which is a regression brought about by layers and layers of fixes over time for a seemingly simple issue - implicitly converting a unique return from a pure function into an immutable.
> ...

Sounds like the code might need a rewrite.

> For another example, it took Martin and I months to implement the new import lookup scheme. It turned out to be fairly complicated, and there were many regressions. There are probably still issues lurking in it.
> ...

Do you have some examples of why it is complicated? (I'm curious whether there is a good way to simplify it.)

> We need to keep the language rules simple enough to understand and simple enough to implement, and there will be compromises with that.

It is however also important to not conflate implementation effort in DMD due to evolvability issues with complexity of the feature. (Of course, pragmatically, it will have some influence on the language design, but ideally it should not.)
October 05, 2017
On Thursday, October 05, 2017 13:36:23 Timon Gehr via Digitalmars-d wrote:
> > On 27 September 2017 at 17:41, Ilya Yaroshenko via Digitalmars-d <digitalmars-d@puremagic.com> wrote: I would prefer outer operator overloading be added to D instead of type wrappers. So a user can import a library for operations, rather then library of wrappers. --Ilya
> "outer operator overloading" is UFCS for operators. I.e.:
>
> struct S{ int x; }
> S opBinary(string op:"+")(S a,S b){ return S(a.x+b.x); }
>
> void main(){
>      auto s=S(3), t=S(4);
>      import std.stdio;
>      writeln(s+t); // S(7)
> }
>
> Starting from:
>
> s+t
>
> It rewritten to (as per the spec):
> s.opBinary!"+"(t)
>
> and then UFCS is applied (as per the spec):
> opBinary!"+"(s,t)
>
>
> I'm very much in favor of this. Also, those rewrites should be
> consistently applied for all types, even built-ins (the compiler
> implementation can be more complex, but the language rules would be
> simplified).
> One immediate benefit would be that opCmp could be reliably used for all
> types that support comparison, for example 2.opCmp(3).
> Another benefit would be that operators such as += can reassign class
> references, for example when a value type is implemented as a unique
> reference to immutable data.

Being able to do 2.opCmp(3) would be pretty cool, but I'm still convinced that allowing for operators to be overloaded outside of the type is a terrible idea. It's far cleaner for them to be tied to the type - especially when you consider that it's not possible to differentiate between conflicting overloadeded operators. And having them declared outside of the type just opens up all of the problems that were just being complained about in this thread with templated code not being able to access free functions that weren't imported in the module that it's in.

- Jonathan M Davis

October 05, 2017
On Thursday, 5 October 2017 at 19:40:05 UTC, Walter Bright wrote:
> On 10/5/2017 6:13 AM, Steven Schveighoffer wrote:
>> On 10/3/17 10:00 PM, Walter Bright wrote:
>>> On 10/3/2017 5:24 PM, Steven Schveighoffer wrote:
>>>> It also can be cheaper to pass a small struct by value.
>>>
>>> Should not be a problem if the compiler inlines it.
>> 
>> That's not always possible.
>
> Right. But then the question becomes how much more complexity do we want to add chasing that last percent of perfection?
>
> For example, right now I'm in my 3rd day of attempting to resolve
>
>   https://issues.dlang.org/show_bug.cgi?id=17635
>
> which is a regression brought about by layers and layers of fixes over time for a seemingly simple issue - implicitly converting a unique return from a pure function into an immutable.
>
> For another example, it took Martin and I months to implement the new import lookup scheme. It turned out to be fairly complicated, and there were many regressions. There are probably still issues lurking in it.
>
> We need to keep the language rules simple enough to understand and simple enough to implement, and there will be compromises with that.

The impression I have, correct me if I'm wrong, is that users- and since you're writing a compiler, your users are developers themselves- pay no attention to simplicity and principles and expect software to always guess correctly about whatever they intended, similar to how dmd offers spelling suggestions for other functions when you typo a function name.

Sometimes this is possible in simple, non-invasive ways like spelling suggestions, but you will go mad trying to create and maintain a complex system that mirrors their contradictory expectations.  Without having delved into the complex import lookup scheme you and Martin set up, I agree with Timon that someone should try to boil it down to some simple principles again.
October 05, 2017
On 10/5/2017 4:26 AM, Timon Gehr wrote:
>> I know that some of the UFCS code was written without regard to hijacking.
>> ...
> 
> I think it is by design. Lookup first tries to find members of the type, and only if they don't exist applies UFCS lookup. Therefore, if members are added to the type or made visible, this may hijack existing UFCS usages.

It may have been done that way to avoid breaking existing code.

> The way to fix it is to do UFCS lookup always and then error out if both UFCS and member lookup match, but there is probably quite some code relying on the fact that you can provide a custom implementation of a general UFCS function by just adding a member. Also, with the hijacking error if you actually meant the member function the only way out I see is to use __traits(getMember, ...) for disambiguation.
> 
> (BTW: Local imports allow hijacking too. Maybe this one could be fixed?)

I thought that was taken care of when the whole import lookup mechanism was redone a year or two ago.


>>> 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 :-)
>> ...
> 
> I'm not used to ADL. I think it is surprising because input ranges support front. ;) (It's not surprising to _me_, I'm rather familiar with D's features, especially those added before last year or so. My statement is more that it could be surprising to many, and that it is not necessarily reasonable to blame them for being surprised.)

When I learn a new language, I am sometimes surprised at the depth of my preconceived notions about how they work that turns out to be very specific to a language I am very used to.


>> 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.
>> ...
> 
> It might make sense to sit down at some point and see what goals the complex rules try to achieve and then come up with simpler rules that achieve the same (or better) goals.

There wasn't a lack of discussion about the import lookup rules :-)


> One thing that currently works is having the following string constant in a util.d file:
> 
> enum ufcs_=q{
>      private{
>          import std.typecons: Proxy;
>          struct UFCS(T){ T payload; mixin Proxy!payload; }
>          auto ufcs(T)(T arg)@trusted{ return *cast(UFCS!T*)&arg; }
>      }
> };
> 
> Then, the following code compiles and runs:
> 
> import util: ufcs_;
> mixin(ufcs_);
> 
> 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.algorithm, std.stdio;
>      iota(0,10).ufcs.each!writeln; // ok
> }
> 
> This exploits what seems to be a serious encapsulation-breaking bug in std.typecons.Proxy in order to pick up all UFCS functions visible from the current module, but a safe version of this could be made.

Nice

>> Doing this kind of wrapper doesn't work for operator overloading, which brings us back to ADL is for operator overloading.
> 
> Why does it not work for operator overloading?

3+s
October 06, 2017
On Thursday, 5 October 2017 at 22:04:10 UTC, Jonathan M Davis wrote:
> It's far cleaner for them to be tied to the type - especially when you consider that it's not possible to differentiate between conflicting overloadeded operators.

In other words, we lose global uniqueness of operators if we were to allow free functions to implement operators.

Knowing which function is called when we see its name is very important for reading code.  That's why we have those anti hijacking rules: they disallow cases where the compiler knows that the call can be misleading (or can silently break existing code).  Another, more tricky case is when there are two functions with the same name in the project you are working on, but only one of them is being imported.  If you read the code, you are unsure which one is called.  The anti-hijacking rules won't work in that case.
Fortunately, a good naming scheme avoids those problems so they are not a big problem in practice.

Those problems will pop up, however, if we allow them for operators.  Without further regulations, different implementations for operators are almost guaranteed, which will lead to uncertainty and distrust in code using operators.



Besides, I don't how it should work without changes to lookup rules.  How should sum() be able to use a +-operator defined in an imported module?  Not even ADL is helping here.

In my opinion, operators should only be defined in the module defining the type.