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