Re: ADL
On Monday, 5 September 2016 at 23:50:33 UTC, Timon Gehr wrote: One hacky way is to provide a mixin template to create a wrapper type within each module that needs it, with std.typecons.Proxy. Proxy picks up UFCS functions in addition to member functions and turns them into member functions. But this leads to a lot of template bloat, because callers that share the same added UFCS functions don't actually share the instantiation. Also, it only works one level deep and automatically generated Wrapper types are generally prone to be somewhat brittle. I don't think cloning a type just to add functionality can possibly be the right way. A C++-style of customizing behavior is using traits. Those traits would be a compile time argument to the algorithm function. Instead of arg.addone() one would use trait.addone(arg). It is not hard to write a proxy that merges trait and arg into one entity, but this should to be done from the callee. The default trait would be type.addone_trait if it exists, or else some default trait that uses all available functions and member function from the module of the type. In most of the cases this is enough, but it enables adding traits to existing types and also different implementations of the same traits. This gets really bloaty in C++, and that's why usually ADL is preferred, but D has the capability to reduce the overhead to a minimum. It doesn't quite make it possible to separate the implementation of types, algorithms and traits (UFCS) into different modules such that they don't know each other. Either the user has to specify the trait each call or either the type's module or the algorithm's module has to import the traits. What I call traits is very similar to type classes in other languages where (among other features) the traits are automatically being attached to the type. (Type classes are also what C++ concepts originally wanted to be.)
Re: ADL
On 05.09.2016 15:35, Andrei Alexandrescu wrote: On 9/5/16 11:43 AM, Timon Gehr wrote: On 05.09.2016 06:05, Manu via Digitalmars-d wrote: An algorithm that calls a function on some T it receives just wants to look near the T; UFCS functions will be there. I agree with your post except for this. In general there could be four modules: one defines the type, one defines the UFCS function, one defines the generic algorithm, and the last one imports the former three. The problem is that D has no standard way to make them work together. Do you think there should? Probably. This kind of pattern has been very successful in other languages. And if so, what would the algorithm be? -- Andrei This is a good question. I'm not sure yet what the best solution is. One hacky way is to provide a mixin template to create a wrapper type within each module that needs it, with std.typecons.Proxy. Proxy picks up UFCS functions in addition to member functions and turns them into member functions. But this leads to a lot of template bloat, because callers that share the same added UFCS functions don't actually share the instantiation. Also, it only works one level deep and automatically generated Wrapper types are generally prone to be somewhat brittle. There is always the default option of requiring the user to create the Wrappers manually, but that's a lot of boilerplate. (Other languages can do analogous things in a very streamlined fashion, by not supporting arbitrary template constraints.)
Re: ADL
On Monday, 5 September 2016 at 13:35:02 UTC, Andrei Alexandrescu wrote: On 9/5/16 11:43 AM, Timon Gehr wrote: On 05.09.2016 06:05, Manu via Digitalmars-d wrote: An algorithm that calls a function on some T it receives just wants to look near the T; UFCS functions will be there. I agree with your post except for this. In general there could be four modules: one defines the type, one defines the UFCS function, one defines the generic algorithm, and the last one imports the former three. The problem is that D has no standard way to make them work together. Do you think there should? And if so, what would the algorithm be? -- Andrei I ran into that very problem many time, and I think it is legitimate. Typical use case is a type from module A that is extended by module B to conform to whatever typeclass is desired (a range for instance) via UFCS. It is not possible to pass it down to anything generic expecting that typeclass is not passed down. However, it is not clear what the solution should be. Tweaking the identifier resolution would make template instance dependent on the instantiation point, which is a non starter (it would cause an explosion is both resources required to compile and a huge amount a binary bloat). On the other hand, I was wondering if it is possible to create a wrapper type type, with alias this, that is dependent on the scope, and that do UFCS resolution is that scope. One could then use the template like : myalgorithm(forwardUFCS(var)); With var the vairable of the type extended to fit the typeclass. I suspect this can be done with a fair amount of mixin magic, but I'm not 100% sure. This way, forwarding the instancier scope would only be done on demand, which would mitigate the instanciation explosion problem one face when doing it as identifier resolution level.
Re: ADL
On 9/5/2016 6:34 AM, Andrei Alexandrescu wrote: ADL would not apply here because it looks up only names in the same module as the type. It would work in C++ because any piece of code can insert more names into any namespace. Inserting names into a namespace violates about every principle of encapsulation I can think of, and it just ain't worth it. It doesn't sound like a good idea. This kind of lookup that collects names from various places and holds an auction is fraught with Byzantine failures. Good lookup is regular and predictable. I agree.
Re: ADL
On 9/5/16 4:41 PM, Jacob Carlborg wrote: On 2016-09-05 15:28, Andrei Alexandrescu wrote: Yah, make front a member please. It's in the same module so you're not breaking any encapsulation anyway. -- Andrei I just said: "I thought one of the reasons for UFCS was to be able to make a type support the range interface without modifying the type" [1]. And you replied: "That is correct" [2]. And now you're saying that it should be a member? That's the path of last resistance. What if it's in the different module? D does not support one module to expand a type defined in another module with 100% transparency. Or as it is for the built-in arrays, not possible to add a member there. That pattern is only possible with the restrictions and limitations of std.array. Andrei
Re: ADL
On 2016-09-05 15:28, Andrei Alexandrescu wrote: Yah, make front a member please. It's in the same module so you're not breaking any encapsulation anyway. -- Andrei I just said: "I thought one of the reasons for UFCS was to be able to make a type support the range interface without modifying the type" [1]. And you replied: "That is correct" [2]. And now you're saying that it should be a member? What if it's in the different module? Or as it is for the built-in arrays, not possible to add a member there. [1] http://forum.dlang.org/post/nqjbu4$1i7h$1...@digitalmars.com [2] http://forum.dlang.org/post/nqjcj6$1j3b$1...@digitalmars.com -- /Jacob Carlborg
Re: ADL
On 9/5/16 11:43 AM, Timon Gehr wrote: On 05.09.2016 06:05, Manu via Digitalmars-d wrote: An algorithm that calls a function on some T it receives just wants to look near the T; UFCS functions will be there. I agree with your post except for this. In general there could be four modules: one defines the type, one defines the UFCS function, one defines the generic algorithm, and the last one imports the former three. The problem is that D has no standard way to make them work together. Do you think there should? And if so, what would the algorithm be? -- Andrei
Re: ADL
On 9/5/16 11:25 AM, Jacob Carlborg wrote: On 2016-09-05 11:06, Andrei Alexandrescu wrote: That is correct (and btw the example should use the member call syntax). But touching a type's module is modifying the type. -- Andrei Not sure what that has to do with anything. Example: module foo; struct Foo { int[] array = [1]; } int front(Foo foo) { return foo.array[0]; } Yah, make front a member please. It's in the same module so you're not breaking any encapsulation anyway. -- Andrei
Re: ADL
On Saturday, 3 September 2016 at 11:24:01 UTC, Walter Bright wrote: On 9/3/2016 3:12 AM, Walter Bright wrote: If you are still determined to use it, you can use: __traits(compiles, ...) like you would SFINAE in C++ to select which of the modules from the argument types selects a function that compiles. Eh, I realized it's simpler than that. Based on the code I already presented, each argument can be used to generate an import for its corresponding version of the function. Then, overloading rules apply and it works. Something like: Something like: void foo(T,U)(T t, U u) { alias func = ModuleOf!T.func; alias func = ModuleOf!U.func; func(t, u); } Can we use a `with` statement? E.g. something along those lines void foo(T, U, alias context = __CURRENT_MODULE__)(T t, U u) { with(context) return func(t, u); }
Re: ADL
On Monday, 5 September 2016 at 01:00:26 UTC, Walter Bright wrote: What about using this template? Sure, it'll work assuming the module imports all its symbols publically, but it's still not as usable as it should be. I still need to invoke it for a number of things, including member variable types. If the member variable is templated, I need to analyse the template arguments for types to import them too. If it is a function, I need to treat each argument as I treat a member variable. I started a thread the other day that touches on another problem I have which this template won't solve: https://forum.dlang.org/thread/wggldyzrbwjboibin...@forum.dlang.org At least in my use cases, it comes down to the template instance not inheriting the visibility of symbols from its template parameters. Which leads to these workarounds. We're aiming for the goal of sub-second compile and reload times for rapid iteration, both with normal code and scripter code. Anything I have to do through templates and CTFE slows compile times down, in some cases significantly.
Re: ADL
On 05.09.2016 06:05, Manu via Digitalmars-d wrote: An algorithm that calls a function on some T it receives just wants to look near the T; UFCS functions will be there. I agree with your post except for this. In general there could be four modules: one defines the type, one defines the UFCS function, one defines the generic algorithm, and the last one imports the former three. The problem is that D has no standard way to make them work together.
Re: ADL
On Monday, 5 September 2016 at 08:17:15 UTC, Andrei Alexandrescu wrote: Are we in agreement about the baseline solution? Yes, but there are a bunch of cases in which the baseline solution is not applicable. Disclaimer: I don't know how C++ would handle the following situation. Let's say I use a library that exposes a type T. Of course the library does not expose a range interface for it. So I create a module t_range, which provides free range functions for type T. This is akin to what Phobos does for arrays, whose range functions are in std.array. Now I want to use std.algorithm on T, as I would use it on arrays. But I can't, because the only reason std.algorithm works on arrays is because it imports std.array. But of course it cannot import my module t_range. What I'd like as a solution is that the template does not only look in its module, but also in the instantiating module, to resolve symbols dependent on the template types. So std.algorithm should not import std.array, but should work on arrays if the instantiating module imports std.array, and should work on T if the instantiating module imports t_range, which is a sound behaviour that would not surprise anyone. Incidentally, this would also solve the problem of emplacing objects with private or package constructors and probably also some of the current discussions about visibility of private members in templates (private members would be visible only if they are visible at the instantiation site) So templates would work as if they were mixed-in the instantiating module, and they would work as if the user explicitly wrote that piece of code specialized for its case directly in the usage site (which is what templates are for: avoiding to write every possible specialization). I know this is a big breaking change that will probably never happen, but I think it would be an interesting scenario and would open lots of possibilities. What do you think?
[OT] local overloading (Was: Re: ADL)
On 05.09.2016 02:50, Walter Bright wrote: On 9/4/2016 2:36 PM, Timon Gehr wrote: Declare-call ordering issues for overload sets are not limited to local scopes. This problem needs to be solved anyway. The fact that the scope is local adds exactly zero additional complications. I know that static if brings with it ordering problems. That's not a justification for adding them to statements. ... I didn't suggest to do that. The sequence can be: 1. Fix ordering problems for overload sets generically. 2. Allow local overloads. or 1. Allow local overloads with some additional restrictions ensuring no ordering problems. 2. Fix ordering problem for overload sets generically. 3. Remove additional restrictions for local overloads. Besides, I showed a method of how the overloads could be done with the existing language. That's not the point. What's perhaps more telling is that you initially got it wrong. It /wants/ to be valid code. Maybe, but if I redesigned the language for every mistake I made, nothing would get done. ... The mistake is arguably in the language design here. (Lack of turtles.) My point with all this is ADL-workalike behavior can be reasonably done with existing D core features available *now* in all 3 compilers. It means we don't have to panic and rewrite the compiler right now - Manu can use these techniques and get his work done, even though it isn't quite what he envisions. He's not dead in the water. Yup.
Re: ADL
On 2016-09-05 11:06, Andrei Alexandrescu wrote: That is correct (and btw the example should use the member call syntax). But touching a type's module is modifying the type. -- Andrei Not sure what that has to do with anything. Example: module foo; struct Foo { int[] array = [1]; } int front(Foo foo) { return foo.array[0]; } module algo; void algorithm(Range)(Range range) { auto e = range.front; // Error: no property 'front' for type 'Foo' } module main import foo; import algo; void main() { algorithm(Foo()); } -- /Jacob Carlborg
Re: ADL
On 9/5/16 10:55 AM, Jacob Carlborg wrote: I thought one of the reasons for UFCS was to be able to make a type support the range interface without modifying the type. That is correct (and btw the example should use the member call syntax). But touching a type's module is modifying the type. -- Andrei
Re: ADL
On 2016-09-05 10:17, Andrei Alexandrescu wrote: Let me make sure I understand it. The core structure is this: = module bob; struct S {} void f(S s); module myalgorithm; void test(T)(T t) { f(t); } = The core issue here is that f is not considered for lookup. It is a free function in the same module as S. That's not a frequent case and it seems right to not support it in the lookup rules. The simplest solution, which has already been discussed, is to make f a member of S. It is important that does not affect modularity; all protection in D has module-level granularity, so the premise of http://www.drdobbs.com/cpp/how-non-member-functions-improve-encapsu/184401197. So it's for the most part a clerical matter: move the body of f inside S, or if f is already generic define an alias for it inside of S. This is the baseline solution, and it is reasonable. This needs to be properly understood before we look into any others: by a simple mechanical intervention, everything works properly. Every language has such minute needs for minor scaffolding. It must also be understood that changing the lookup rules to make this scaffolding unnecessary bring with them a host of unpleasant consequences. Are we in agreement about the baseline solution? I thought one of the reasons for UFCS was to be able to make a type support the range interface without modifying the type. -- /Jacob Carlborg
Re: ADL
On 9/5/16 10:17 AM, Andrei Alexandrescu wrote: so the premise of http://www.drdobbs.com/cpp/how-non-member-functions-improve-encapsu/184401197 ... "does not apply". -- Andrei
Re: ADL
On 9/5/16 6:23 AM, Manu via Digitalmars-d wrote: But the point of my post is that I feel the problem is of very high importance. Let me make sure I understand it. The core structure is this: = module bob; struct S {} void f(S s); module myalgorithm; void test(T)(T t) { f(t); } = The core issue here is that f is not considered for lookup. It is a free function in the same module as S. That's not a frequent case and it seems right to not support it in the lookup rules. The simplest solution, which has already been discussed, is to make f a member of S. It is important that does not affect modularity; all protection in D has module-level granularity, so the premise of http://www.drdobbs.com/cpp/how-non-member-functions-improve-encapsu/184401197. So it's for the most part a clerical matter: move the body of f inside S, or if f is already generic define an alias for it inside of S. This is the baseline solution, and it is reasonable. This needs to be properly understood before we look into any others: by a simple mechanical intervention, everything works properly. Every language has such minute needs for minor scaffolding. It must also be understood that changing the lookup rules to make this scaffolding unnecessary bring with them a host of unpleasant consequences. Are we in agreement about the baseline solution? Andrei
Re: ADL
On 9/4/2016 9:23 PM, Manu via Digitalmars-d wrote: I already worked-around my problems. But the point of my post is that I feel the problem is of very high importance. I don't think the situation is okay, since we're making design recommendations that lead straight to these problems. And these modern D design patterns are the thing in D I'm most excited about, and keen to share with not-yet-D-users. Try the solutions I proposed - they aren't the ones you have been using. Give 'em a chance! As pointed out, C++ ADL is an awkward feature with ugly corner cases. If we add it to D, we'll be forevermore stuck with that. The library solutions presented here do work, and don't suffer from those problems.
Re: ADL
On 5 September 2016 at 10:50, Walter Bright via Digitalmars-d <digitalmars-d@puremagic.com> wrote: > On 9/4/2016 2:36 PM, Timon Gehr wrote: >> >> Declare-call ordering issues for overload sets are not limited to local >> scopes. >> This problem needs to be solved anyway. The fact that the scope is local >> adds >> exactly zero additional complications. > > > I know that static if brings with it ordering problems. That's not a > justification for adding them to statements. > > >>> Besides, I showed a method of how the overloads could be done with the >>> existing language. >> >> That's not the point. What's perhaps more telling is that you initially >> got it >> wrong. It /wants/ to be valid code. > > > Maybe, but if I redesigned the language for every mistake I made, nothing > would get done. > > My point with all this is ADL-workalike behavior can be reasonably done with > existing D core features available *now* in all 3 compilers. It means we > don't have to panic and rewrite the compiler right now - Manu can use these > techniques and get his work done, even though it isn't quite what he > envisions. He's not dead in the water. I already worked-around my problems. But the point of my post is that I feel the problem is of very high importance. I don't think the situation is okay, since we're making design recommendations that lead straight to these problems. And these modern D design patterns are the thing in D I'm most excited about, and keen to share with not-yet-D-users.
Re: ADL
On 5 September 2016 at 14:05, Manu <turkey...@gmail.com> wrote: > > I've seen this one. Again, we're not talking about C++. It hasn't been > explored (to my knowledge) how a similar mechanism it would look and > affect D. I suspect (with no evidence) it would be relatively benign > by comparison to the problems ADL introduces to C++, and D stands to > gain a lot more from the transaction, ie, UFCS will work in generic > functions the same as non-generic functions. D requires this much more > than C++ does, particularly when you take this as the direction of > forward momentum for D design. Anyway, I've made my case. I'm watching to see where this goes. I don't really have anything to add, so I'll let it be from here.
Re: ADL
On 4 September 2016 at 07:38, Andrei Alexandrescu via Digitalmars-d <digitalmars-d@puremagic.com> wrote: > On 9/3/16 11:31 AM, Manu via Digitalmars-d wrote: >>> >>> > In any case, these difficulties are the consequence of trying to write >>> > C++ >>> > code in D. >> >> You've told me this before, and I find it kind of offensive every time >> you do, since I've been programming D for like 7 years now, and I >> definitely don't code D like C++. >> If anything, I have a strong tendency to code C++ like D, and that has >> lead to a lot of interesting changes in my C++ style. >> >> I'm tired of these sorts of dismissals. You insist that I'm not a >> 'real' D programmer, or something to that effect. > > > There's really no need to take offense here. We have a bit of a mimosa > culture of easily bruised egos it seems. As the joke goes: "You take offense > too easily." "I can't believe you just said that!" > > It's hard to not see your view as coming straight from the "I want to speak > Italian by replacing English words with Italian words" box. > > * It hypothesizes that one entire style of design is completely impossible > because it lacks one obscure feature from C++. And you did it again. It's the insistence that I'm trying to write C++ in D that's really annoying. (it's not the first time) I'm writing textbook modern D code. It's a style of design that is absolutely impossible in C++, ergo, I'm writing D code! The thing I'm trying to present is that it doesn't work intuitively. The whole reason I came here to comment is because I realised that D's central modern design pattern, that is, algorithms + UFCS (ie, pipeline style; *impossible in C++*) doesn't quite work properly, and it can be hard to understand why. UFCS needs to 'work', not 'sometimes work'. Let's forget I ever said C++ or ADL. I only raised it because I wondered why this wasn't a problem in C++, and then realised that ADL existed in C++. > * Vast evidence to the contrary is ignored. To the contrary of what? That UFCS from within a template doesn't work in a practical way? Nobody has presented any solution or workaround that doesn't require manual user intervention. A solution like: `adl(string fn, T, Args...)(auto ref T x, auto ref Args args)` has appeared on the table. I find it hard to accept criticism for my opinion when that's the proposed solution. That is a clear demonstration of the problem, not a solution. The logical conclusion of this is that all algorithm implementations have to call all functions this way if you want UFCS to work the way users would expect... but users would not expect to call functions in this way either. > * The fact that the feature is problematic and controversial even within C++ > circles is also neglected. I haven't commented in ADL's merits in C++, just that it's the mechanism by which C++ doesn't exhibit this (similar) problem which is much more significant in D than it is in C++. D needs a solution otherwise UFCS appears a weak language offering where it breaks down when used in algorithm functions, and I quite strongly resist the idea that the best solution is manual user intervention. > * The lack of said feature is regarded as an utter disaster and no > workaround is even close to cutting the mustard. I do think it's an absolute disaster. It's a core premise of modern D, algorithms and UFCS are central. I'd almost say they *are* modern D. I don't think core language offerings and design recommendations should require 'work arounds', that sounds like something that's just broken by design. You're right, that doesn't cut the mustard, and I'd be extremely disappointed to accept that as 'solved'. > * A language change is a must; no library solution would ever be acceptable. Library solution implies user-intervention. My argument is that I find the notion of user intervention in this case unacceptable. I think it's too fundamental to require work-arounds. I'm happy to be proven wrong, but I'm not gonna concede on that point easily. > For the most part this is a Pop a level up and figure what the needed > accomplishment is, so other routes than the ADL bottleneck are possible. > Let's approach this together like a problem that has a solution within D, > and let's make that solution accessible comfortably. Sure... but that doesn't mean I'm just going to accept the proposed workarounds as 'fixed'. I find the proposed workarounds really unpleasant, and I don't want to see that proliferate. I'd rather resist an idea I hate now, than see it take root. If I back off, and they become a thing, then who do I have to blame? > Two apocryphal anecdotes: Koenig invented the eponymous lookup as a > consequence to the fact that namespaces broke the "Hello, world" program > (som
Re: ADL
On 9/4/2016 1:48 PM, Ethan Watson wrote: Chipping in to say that I currently do something this with Binderoo templates... and it sucks. What about using this template? // Find module in which T was defined template ModuleOf(alias T) { import std.traits : moduleName; mixin("import " ~ moduleName!T ~ ";"); mixin("alias ModuleOf = " ~ moduleName!T ~ ";"); } and it only has to be written once. Yes, it uses CTFE and mixins, but it's all hidden away inside the template.
Re: ADL
On 9/4/2016 2:36 PM, Timon Gehr wrote: Declare-call ordering issues for overload sets are not limited to local scopes. This problem needs to be solved anyway. The fact that the scope is local adds exactly zero additional complications. I know that static if brings with it ordering problems. That's not a justification for adding them to statements. Besides, I showed a method of how the overloads could be done with the existing language. That's not the point. What's perhaps more telling is that you initially got it wrong. It /wants/ to be valid code. Maybe, but if I redesigned the language for every mistake I made, nothing would get done. My point with all this is ADL-workalike behavior can be reasonably done with existing D core features available *now* in all 3 compilers. It means we don't have to panic and rewrite the compiler right now - Manu can use these techniques and get his work done, even though it isn't quite what he envisions. He's not dead in the water.
Re: ADL
On 04.09.2016 22:22, Walter Bright wrote: On 9/4/2016 5:30 AM, Andrei Alexandrescu wrote: Might be a sensible enhancement. Removing artificial limitations is good programming language design. Turtles! -- Andrei The design of executable function bodies is very much "declare before use", quite unlike at the declaration levels which is all "order is not relevant". Changing this will have consequences (such as our discussion of exactly when a declaration becomes valid), The rule should be the same as for module-level functions. Note that the rules for module-level functions are currently inadequate: pragma(msg, foo(0)); // calls double overload static if(foo(0)){ // calls double overload bool foo(int x){ return false; } } bool foo(double x){ return true; } pragma(msg, foo(0)); // calls int overload Declare-call ordering issues for overload sets are not limited to local scopes. This problem needs to be solved anyway. The fact that the scope is local adds exactly zero additional complications. and I just feel that function code is just easier to understand if "declare before use" is the rule, Overloading does not violate declare before use. You seem to be conflating it with forward referencing. because that is how we reason about how it is executed. Besides, I showed a method of how the overloads could be done with the existing language. That's not the point. What's perhaps more telling is that you initially got it wrong. It /wants/ to be valid code.
Re: ADL
On Saturday, 3 September 2016 at 01:09:18 UTC, Walter Bright wrote: Fourth solution: module myalgorithm; void test(T)(T t) { import std.traits; mixin("import " ~ std.traits.moduleName!T ~ ";"); mixin("alias M = " ~ std.traits.moduleName!T ~ ";"); // The above could be encapsulated into an eponymous template // that takes T as a parameter and returns the alias M.f(t); } Chipping in to say that I currently do something this with Binderoo templates... and it sucks. https://github.com/Remedy-Entertainment/binderoo/blob/master/binderoo_client/d/src/binderoo/variabledescriptor.d One example is in there, the VariableDescriptors eponymous template, where a template that collects every member variable of an object has to mix in the module names of each encountered member variable type to stop the compiler complaining about module visibility. So I'm doing the double whammy of taxing the template expansion engine and the CTFE engine. It could be that switching it to a mixin template (and working out someway to make it as usable as eponymous templates) will solve the problem - but the way this codebase is going it's going to mean every template needs to be a mixin. Surely the base template system can be more flexible than this?
Re: ADL
On 9/4/2016 5:30 AM, Andrei Alexandrescu wrote: Might be a sensible enhancement. Removing artificial limitations is good programming language design. Turtles! -- Andrei The design of executable function bodies is very much "declare before use", quite unlike at the declaration levels which is all "order is not relevant". Changing this will have consequences (such as our discussion of exactly when a declaration becomes valid), and I just feel that function code is just easier to understand if "declare before use" is the rule, because that is how we reason about how it is executed. Besides, I showed a method of how the overloads could be done with the existing language.
Re: ADL
On 9/4/2016 5:31 AM, Andrei Alexandrescu wrote: On 9/4/16 3:26 AM, Walter Bright wrote: On 9/3/2016 4:46 PM, Andrei Alexandrescu wrote: On 9/4/16 12:28 AM, Walter Bright wrote: On 9/3/2016 1:43 PM, Andrei Alexandrescu wrote: On 9/3/16 10:43 PM, Walter Bright wrote: On 9/3/2016 8:34 AM, Manu via Digitalmars-d wrote: And if either module doesn't have an instance of func? static if (traits(compiles, ModuleOf!T.func)) alias func = ModuleOf!T.func; What's an elegant way to encapsulate this as a stutter-free one-liner? -- Andrei using a template and mixing it in "Show me the mo^H^Hcode." template foo(T) { static if (traits(compiles, ModuleOf!T.func)) foo = "alias func = " ~ ModuleOf!T.func ~ ";"; else foo = ""; } mixin(foo!T); Actually "func" i.e. the function's name must be a paraneter. No? -- Andrei Probably. The point is, the tools to do this are available features of D.
Re: ADL
On 9/4/16 3:26 AM, Walter Bright wrote: On 9/3/2016 4:46 PM, Andrei Alexandrescu wrote: On 9/4/16 12:28 AM, Walter Bright wrote: On 9/3/2016 1:43 PM, Andrei Alexandrescu wrote: On 9/3/16 10:43 PM, Walter Bright wrote: On 9/3/2016 8:34 AM, Manu via Digitalmars-d wrote: And if either module doesn't have an instance of func? static if (traits(compiles, ModuleOf!T.func)) alias func = ModuleOf!T.func; What's an elegant way to encapsulate this as a stutter-free one-liner? -- Andrei using a template and mixing it in "Show me the mo^H^Hcode." template foo(T) { static if (traits(compiles, ModuleOf!T.func)) foo = "alias func = " ~ ModuleOf!T.func ~ ";"; else foo = ""; } mixin(foo!T); Actually "func" i.e. the function's name must be a paraneter. No? -- Andrei
Re: ADL
On 9/4/16 2:36 AM, Timon Gehr wrote: On 03.09.2016 13:24, Walter Bright wrote: Something like: void foo(T,U)(T t, U u) { alias func = ModuleOf!T.func; alias func = ModuleOf!U.func; func(t, u); } Does not work. Local overloads are not supported. Might be a sensible enhancement. Removing artificial limitations is good programming language design. Turtles! -- Andrei
Re: ADL
On 9/3/2016 10:57 PM, ZombineDev wrote: What do you think about making overloading and UFCS work with local symbols? I'd rather not. Let's make what we have work. There's an unending demand for new features in the core language. > There are workarounds, but nothing pretty. I don't regard this as a workaround. It's how things were designed to work in D. The fact that it isn't the minimum number of characters doesn't make it a workaround.
Re: ADL
On Sunday, 4 September 2016 at 01:34:47 UTC, Walter Bright wrote: On 9/3/2016 5:36 PM, Timon Gehr wrote: Does not work. Local overloads are not supported. Yeah, you're right, should have tested that: void abc(int); void def(uint); void foo() { alias func = abc; alias func = def; // error func(1); } fails. Pushing it out a level works: void abc(int); void def(uint); template foo() { alias func = abc; alias func = def; void foo() { func(1); } } void main() { foo(); } What do you think about making overloading and UFCS work with local symbols? There are workarounds, but nothing pretty.
Re: ADL
On 9/3/2016 5:36 PM, Timon Gehr wrote: Does not work. Local overloads are not supported. Yeah, you're right, should have tested that: void abc(int); void def(uint); void foo() { alias func = abc; alias func = def; // error func(1); } fails. Pushing it out a level works: void abc(int); void def(uint); template foo() { alias func = abc; alias func = def; void foo() { func(1); } } void main() { foo(); }
Re: ADL
On 9/3/2016 4:46 PM, Andrei Alexandrescu wrote: On 9/4/16 12:28 AM, Walter Bright wrote: On 9/3/2016 1:43 PM, Andrei Alexandrescu wrote: On 9/3/16 10:43 PM, Walter Bright wrote: On 9/3/2016 8:34 AM, Manu via Digitalmars-d wrote: And if either module doesn't have an instance of func? static if (traits(compiles, ModuleOf!T.func)) alias func = ModuleOf!T.func; What's an elegant way to encapsulate this as a stutter-free one-liner? -- Andrei using a template and mixing it in "Show me the mo^H^Hcode." template foo(T) { static if (traits(compiles, ModuleOf!T.func)) foo = "alias func = " ~ ModuleOf!T.func ~ ";"; else foo = ""; } mixin(foo!T);
Re: ADL
On 03.09.2016 13:24, Walter Bright wrote: Something like: void foo(T,U)(T t, U u) { alias func = ModuleOf!T.func; alias func = ModuleOf!U.func; func(t, u); } Does not work. Local overloads are not supported.
Re: ADL
On 9/4/16 12:28 AM, Walter Bright wrote: On 9/3/2016 1:43 PM, Andrei Alexandrescu wrote: On 9/3/16 10:43 PM, Walter Bright wrote: On 9/3/2016 8:34 AM, Manu via Digitalmars-d wrote: And if either module doesn't have an instance of func? static if (traits(compiles, ModuleOf!T.func)) alias func = ModuleOf!T.func; What's an elegant way to encapsulate this as a stutter-free one-liner? -- Andrei using a template and mixing it in "Show me the mo^H^Hcode."
Re: ADL
On 9/3/2016 3:35 AM, Walter Bright wrote: That isn't how it works in C++. It's done right up front in finding the candidates for overloading, not as a fallback. That statement is incorrect. It's used as a fallback in C++. I had forgotten.
Re: ADL
On 9/3/2016 1:43 PM, Andrei Alexandrescu wrote: On 9/3/16 10:43 PM, Walter Bright wrote: On 9/3/2016 8:34 AM, Manu via Digitalmars-d wrote: And if either module doesn't have an instance of func? static if (traits(compiles, ModuleOf!T.func)) alias func = ModuleOf!T.func; What's an elegant way to encapsulate this as a stutter-free one-liner? -- Andrei using a template and mixing it in
Re: ADL
On 9/3/16 11:31 AM, Manu via Digitalmars-d wrote: > In any case, these difficulties are the consequence of trying to write C++ > code in D. You've told me this before, and I find it kind of offensive every time you do, since I've been programming D for like 7 years now, and I definitely don't code D like C++. If anything, I have a strong tendency to code C++ like D, and that has lead to a lot of interesting changes in my C++ style. I'm tired of these sorts of dismissals. You insist that I'm not a 'real' D programmer, or something to that effect. There's really no need to take offense here. We have a bit of a mimosa culture of easily bruised egos it seems. As the joke goes: "You take offense too easily." "I can't believe you just said that!" It's hard to not see your view as coming straight from the "I want to speak Italian by replacing English words with Italian words" box. * It hypothesizes that one entire style of design is completely impossible because it lacks one obscure feature from C++. * Vast evidence to the contrary is ignored. * The fact that the feature is problematic and controversial even within C++ circles is also neglected. * The lack of said feature is regarded as an utter disaster and no workaround is even close to cutting the mustard. * A language change is a must; no library solution would ever be acceptable. For the most part this is a Pop a level up and figure what the needed accomplishment is, so other routes than the ADL bottleneck are possible. Let's approach this together like a problem that has a solution within D, and let's make that solution accessible comfortably. Two apocryphal anecdotes: Koenig invented the eponymous lookup as a consequence to the fact that namespaces broke the "Hello, world" program (something to do with cout and endl being migrated to namespace std), which surprised the inner circles of C++ the way putting mentos in coke surprises the unwary. Namespaces are an unusually poorly designed feature of C++, not second even to exceptions. By that time significant work had been invested in defining and implementing namespaces, so going back wasn't quite an option. ADL was hailed as a horrible hack but a lifesaving one. Compilers have been slow to implement ADL, partly because essentially it meant breaking a lookup algorithm that was relatively orderly and DWIM, and replace it with a patchwork of special cases. The community has since learned to live with the odd idioms that make code work with the new rules. Second anecdote: Scott Meyers had an infamous talk on C++ in which the leitmotif was "What does f(x) mean?" Scott asks the question in the beginning to which the obvious response is "Call function f passing it argument x". By the time his talk is done, the horrified audience realizes they have no idea what f(x) means and where it goes. Andrei
Re: ADL
On 9/3/2016 9:24 AM, Andrei Alexandrescu wrote: On 9/3/16 5:57 PM, Manu via Digitalmars-d wrote: It's not a problem I've ever had. A problem you didn't know you have. It's a classic C++ conundrum combining theory and practice. One thing I've noticed in my years of programming and helping others with their code is that one can use a feature for decades, over and over, but are only using it in a certain specific way. One is lulled into thinking that it works in the general case because one is not aware that one's usage is constrained. Someone else comes along, uses it slightly differently, and oops. "But I always took the left fork!" :-) It bites me all the time.
Re: ADL
On 9/3/2016 6:22 AM, ZombineDev wrote: I agree that it's not a template issue. It's more of a modules vs namespaces issue. I think the lack of ADL is not a problem in C# because everyone can (and everyone does) extend an existing namespace, so most user's of LINQ algorithms just slap a `using System.Linq` on top of the file without caring in exactly which file the functionality they're using is coming from. Hmm, that explanation could be it. I'm not very familiar with LINQ.
Re: ADL
On 9/3/2016 8:51 AM, Manu via Digitalmars-d wrote: This is exactly the difference between std.algorithm, and what I was trying to express as an algorithm that 'does work'. It's not the business of the API for the user to supply the work to do (ie, via lambda); the function is meant to do the work, which means it needs to call other functions. There are no lambdas to be seen in this situation. This is purely a stylistic issue. I, on the other hand, think lambdas are a superior design, as ADL lookups never sat well with me because it can be quite difficult for the user to figure out where 'func' is coming from. (Besides, I showed how other scopes can be imported based on a type, and then things can be looked up in those scopes, and UFCS applied.) I still think that's unnecessarily complicated, and multiple arguments leads to static if-ing and __traits(compiles,...). The fact the user needs to intervene at all is already too much. I already showed how your previous objection to this could be folded away inside a nice template. Apparently I need to stress again, this is a *core value proposition of D*... It's presented as "this is modern D code", and yet it's awkward and requires careful handling or you get hard to understand name-resolution issues. As mentioned, I don't see anything hard to understand about it - it makes it clear to the reader where names are coming from. ADL pulls names in from who knows where. UFCS *is* modern D. Algorithms and ranges *is* modern D. Seriously, this is the style that modern D aspires to, and it doesn't 'just work'. There should be ear-piercing alarms and flashing red everywhere. This comes up for me frequently, yet ADL has never caused me a single moments trouble, in 15+ years. I didn't even know ADL existed until I started running into this problem in D and then wondered to myself why I never encountered the same issue in C++. It worked so seamlessly and intuitively, I didn't even know it was there. I don't care if the solution is ADL like C++, or something else that works, just that this problem is real; it's a massive fly in the ointment of modern D style, and I don't think it's acceptable. It needs a seamless solution, not manual intervention at every case. D depends on this so much more than C++ does. You've used ADL for 15 years. You're obviously very comfortable with it. I suggest trying out using lambdas enough to feel comfortable with it before deciding that ADL is better. New coding styles are rarely comfortable right off the bat.
Re: ADL
On 9/3/16 10:43 PM, Walter Bright wrote: On 9/3/2016 8:34 AM, Manu via Digitalmars-d wrote: And if either module doesn't have an instance of func? static if (traits(compiles, ModuleOf!T.func)) alias func = ModuleOf!T.func; What's an elegant way to encapsulate this as a stutter-free one-liner? -- Andrei
Re: ADL
On 9/3/2016 8:34 AM, Manu via Digitalmars-d wrote: And if either module doesn't have an instance of func? static if (traits(compiles, ModuleOf!T.func)) alias func = ModuleOf!T.func;
Re: ADL
On 9/3/16 10:29 PM, Walter Bright wrote: On 9/3/2016 6:04 AM, Andrei Alexandrescu wrote: This only works with the respective modules do define `func`. We need something that conditionally plants the symbol depending on whether the module defines it or not. -- Andrei That's where __traits(compiles, ...) comes in. It can be encapsulated in another template. Yah. That we need to plant in the standard library. -- Andrei
Re: ADL
On 9/3/2016 9:01 AM, Manu via Digitalmars-d wrote: Right, and it also has to not conflict with possible local definitions, or instances supplied by imports in the local namespace. Ie, the module where T came from is *an additional* place to look, not *the* place to look. I expect that local definitions may exist for things like generic fallbacks, or primitive/builtin type implementations. 1. 'alias func = ...;' also works to bring in local definitions to an even footing with the other alias func statements. 2. Due to recent changes to import lookup rules, imports are searched if locals do not satisfy the lookup. 3. You can use traits(compiles, ...) to not insert the names in the local scope if it already can be looked up. So I believe you're good to go.
Re: ADL
On 9/3/16 7:38 PM, Tobias Müller wrote: Andrei Alexandrescuwrote: On 9/3/16 7:08 PM, Tobias M wrote: On Saturday, 3 September 2016 at 16:33:07 UTC, Andrei Alexandrescu wrote: I see. This is a matter orthogonal to DbI - introspection should be able to figure out whether a member can be found, or a nonmember if the design asks for it. I wouldn't like "tricking" DbI into thinking a member is there when there isn't. -- Andrei The problem I see with DbI is rather that the user of a function thinks that an optional constraint is satisfied, while in reality it isn't, due to a non-obvious lookup/visibility problem. At some point there's a need to RTFM. -- Andrei Is there one for DbI? (Sincere question) Not yet. We have the allocators body of work, but that's too niche to serve as a general example. I think std.experimental.checkedint will be the canonical example on how to do DbI. I'll propose a blog post to Mike. -- Andrei
Re: ADL
On 9/3/2016 6:04 AM, Andrei Alexandrescu wrote: This only works with the respective modules do define `func`. We need something that conditionally plants the symbol depending on whether the module defines it or not. -- Andrei That's where __traits(compiles, ...) comes in. It can be encapsulated in another template.
Re: ADL
ZombineDev <petar.p.ki...@gmail.com> wrote: > So what? C#'s generics are less flexible than C++ and D templates. > The point is that C#'s lookup does not consider only the > implemented interfaces, but also falls back to extensions > methods. If C# had ADL, > the compiler would also look for extension methods in the > namespace > of the type (in non-generic methods, when the type is "known"), > although the user of the type may not have imported the namespace. ADL wouldn't change anything if you don't cast to a specific type, and if you do, that part of the code is not generic anymore. >>> Sum is implemented in that stupid way, because unlike C++, in >>> C# operators need to be implemented as static methods, so you >>> can't abstract them with an interface. If they were instance >>> methods, you could implement them outside of the class as >>> extension methods and there would be no need to write a >>> distinct method for each type. Here's an example: >>> http://rextester.com/PQFPC46087 >>> The only thing missing is syntax sugar to forward the '+' >>> operator to 'Add' in my example. >> >> With runtime reflection you can do almost anything... That's >> circumventing the type system and doesn't disprove anything. > > There's no circumventing the type system. `typeof(obj)` is barely > even reflection. You can do this with regular cast or using the > `is` expression (http://rextester.com/CXGNK69048). I used > `typeof` just because it could yield better performance. Typecasting *is* circumventing the type system.
Re: ADL
On Saturday, 3 September 2016 at 17:05:35 UTC, Tobias M wrote: On Saturday, 3 September 2016 at 16:32:16 UTC, ZombineDev wrote: No you're wrong. There's no need for interfaces or for generic constraints. It's not static vs duck typing. It's just a method lookup issue. See for yourself: http://rextester.com/GFKNSK99121 Ok, Interfaces and other generic methods with compatible constraints. But in the end you cannot do much without any interface constraints except writing out to the console as you do in the example. But the main point still holds, name lookup is only done at definition time, not at instantiation time. That's why you can only call generic methods. Overloads don't work. So what? C#'s generics are less flexible than C++ and D templates. The point is that C#'s lookup does not consider only the implemented interfaces, but also falls back to extensions methods. If C# had ADL, the compiler would also look for extension methods in the namespace of the type (in non-generic methods, when the type is "known"), although the user of the type may not have imported the namespace. Sum is implemented in that stupid way, because unlike C++, in C# operators need to be implemented as static methods, so you can't abstract them with an interface. If they were instance methods, you could implement them outside of the class as extension methods and there would be no need to write a distinct method for each type. Here's an example: http://rextester.com/PQFPC46087 The only thing missing is syntax sugar to forward the '+' operator to 'Add' in my example. With runtime reflection you can do almost anything... That's circumventing the type system and doesn't disprove anything. There's no circumventing the type system. `typeof(obj)` is barely even reflection. You can do this with regular cast or using the `is` expression (http://rextester.com/CXGNK69048). I used `typeof` just because it could yield better performance. I mean, it even "works" for types that cannot be added at all, by just returning a default value... ? Sorry, I don't understand, what's the problem?
Re: ADL
Andrei Alexandrescuwrote: > On 9/3/16 7:08 PM, Tobias M wrote: >> On Saturday, 3 September 2016 at 16:33:07 UTC, Andrei Alexandrescu wrote: >>> I see. This is a matter orthogonal to DbI - introspection should be >>> able to figure out whether a member can be found, or a nonmember if >>> the design asks for it. I wouldn't like "tricking" DbI into thinking a >>> member is there when there isn't. -- Andrei >> >> The problem I see with DbI is rather that the user of a function thinks >> that an optional constraint is satisfied, while in reality it isn't, due >> to a non-obvious lookup/visibility problem. > > At some point there's a need to RTFM. -- Andrei Is there one for DbI? (Sincere question)
Re: ADL
On 9/3/16 7:08 PM, Tobias M wrote: On Saturday, 3 September 2016 at 16:33:07 UTC, Andrei Alexandrescu wrote: I see. This is a matter orthogonal to DbI - introspection should be able to figure out whether a member can be found, or a nonmember if the design asks for it. I wouldn't like "tricking" DbI into thinking a member is there when there isn't. -- Andrei The problem I see with DbI is rather that the user of a function thinks that an optional constraint is satisfied, while in reality it isn't, due to a non-obvious lookup/visibility problem. At some point there's a need to RTFM. -- Andrei
Re: ADL
On 9/3/16 7:00 PM, vit wrote: On Saturday, 3 September 2016 at 13:04:30 UTC, Andrei Alexandrescu wrote: On 9/3/16 1:24 PM, Walter Bright wrote: On 9/3/2016 3:12 AM, Walter Bright wrote: If you are still determined to use it, you can use: __traits(compiles, ...) like you would SFINAE in C++ to select which of the modules from the argument types selects a function that compiles. Eh, I realized it's simpler than that. Based on the code I already presented, each argument can be used to generate an import for its corresponding version of the function. Then, overloading rules apply and it works. Something like: Something like: void foo(T,U)(T t, U u) { alias func = ModuleOf!T.func; alias func = ModuleOf!U.func; func(t, u); } This only works with the respective modules do define `func`. We need something that conditionally plants the symbol depending on whether the module defines it or not. -- Andrei perhaps this: auto adl(string fn, T, Args...)(auto ref T x, auto ref Args args) Perhaps too surgical (although nice to have as an option). We need something that pulls the symbol for all purposes. -- Andrei
Re: ADL
Tobias Mwrote: > On Saturday, 3 September 2016 at 16:32:16 UTC, ZombineDev wrote: >> Sum is implemented in that stupid way, because unlike C++, in >> C# operators need to be implemented as static methods, so you >> can't abstract them with an interface. If they were instance >> methods, you could implement them outside of the class as >> extension methods and there would be no need to write a >> distinct method for each type. Here's an example: >> http://rextester.com/PQFPC46087 >> The only thing missing is syntax sugar to forward the '+' >> operator to 'Add' in my example. > > With runtime reflection you can do almost anything... That's > circumventing the type system and doesn't disprove anything. > I mean, it even "works" for types that cannot be added at all, by > just returning a default value... It's not runtime reflection, sorry about that. But Add claims to be generic but it's actually just a list of special cases. It compiles for all types but only works for some. And even worse, for types that actually do support addition but are not in the list it silently does the wrong thing. You cannot do the same in a truly generic way.
Re: ADL
On Saturday, 3 September 2016 at 16:32:16 UTC, ZombineDev wrote: No you're wrong. There's no need for interfaces or for generic constraints. It's not static vs duck typing. It's just a method lookup issue. See for yourself: http://rextester.com/GFKNSK99121 Ok, Interfaces and other generic methods with compatible constraints. But in the end you cannot do much without any interface constraints except writing out to the console as you do in the example. But the main point still holds, name lookup is only done at definition time, not at instantiation time. That's why you can only call generic methods. Overloads don't work. Sum is implemented in that stupid way, because unlike C++, in C# operators need to be implemented as static methods, so you can't abstract them with an interface. If they were instance methods, you could implement them outside of the class as extension methods and there would be no need to write a distinct method for each type. Here's an example: http://rextester.com/PQFPC46087 The only thing missing is syntax sugar to forward the '+' operator to 'Add' in my example. With runtime reflection you can do almost anything... That's circumventing the type system and doesn't disprove anything. I mean, it even "works" for types that cannot be added at all, by just returning a default value...
Re: ADL
On Saturday, 3 September 2016 at 16:33:07 UTC, Andrei Alexandrescu wrote: I see. This is a matter orthogonal to DbI - introspection should be able to figure out whether a member can be found, or a nonmember if the design asks for it. I wouldn't like "tricking" DbI into thinking a member is there when there isn't. -- Andrei The problem I see with DbI is rather that the user of a function thinks that an optional constraint is satisfied, while in reality it isn't, due to a non-obvious lookup/visibility problem.
Re: ADL
On Saturday, 3 September 2016 at 13:04:30 UTC, Andrei Alexandrescu wrote: On 9/3/16 1:24 PM, Walter Bright wrote: On 9/3/2016 3:12 AM, Walter Bright wrote: If you are still determined to use it, you can use: __traits(compiles, ...) like you would SFINAE in C++ to select which of the modules from the argument types selects a function that compiles. Eh, I realized it's simpler than that. Based on the code I already presented, each argument can be used to generate an import for its corresponding version of the function. Then, overloading rules apply and it works. Something like: Something like: void foo(T,U)(T t, U u) { alias func = ModuleOf!T.func; alias func = ModuleOf!U.func; func(t, u); } This only works with the respective modules do define `func`. We need something that conditionally plants the symbol depending on whether the module defines it or not. -- Andrei perhaps this: auto adl(string fn, T, Args...)(auto ref T x, auto ref Args args){ import std.traits : moduleName, hasMember; import std.meta : Filter, NoDuplicates, staticMap; import std.array : join; static if(hasMember!(T, fn)){ mixin("return x." ~ fn ~ "(args);"); } else{ enum toImportString(T) = "import " ~ moduleName!(T) ~ " : " ~ fn ~ ";"; enum hasModuleFN(T) = __traits(compiles, mixin("(){" ~ toImportString!T ~ "}")); alias Types = Filter!(hasModuleFN, NoDuplicates!(T, Args)); static assert(Types.length, "no property '" ~ fn ~ "' for type '" ~ __traits(identifier, T)~ "'"); mixin([staticMap!(toImportString, Types),"return " ~ fn ~ "(x, args);"].join("\n")); } }
Re: ADL
On 9/3/16 4:09 PM, Tobias M wrote: On Saturday, 3 September 2016 at 12:25:11 UTC, Andrei Alexandrescu wrote: What problems are you referring to? -- Andrei The problems discussed here in the thread related to name lookup at template instantiation time. And also similar problems related to visibility (public/private) that were discussed in a different thread recently. I see. This is a matter orthogonal to DbI - introspection should be able to figure out whether a member can be found, or a nonmember if the design asks for it. I wouldn't like "tricking" DbI into thinking a member is there when there isn't. -- Andrei
Re: ADL
On Saturday, 3 September 2016 at 14:05:11 UTC, Tobias M wrote: On Saturday, 3 September 2016 at 12:40:26 UTC, ZombineDev wrote: No, LINQ doesn't work because of interfaces, but because of extension methods (C#'s variant of UFCS). The IEnumerable interface defines only a single method. All the useful functionality is implemented as extension methods which are only available if the user specifically imports the namespace in which where they're defined (just like D's ranges and range primitive implementations for arrays). Those extension methods are used as a fallback, similarly to UFCS in D: every type can override the extension methods by implementing the method itself. Also more inner namespaces (more closer to the method invocation) override more outer namespaces. I know extension methods, that's not the point. The point is, that you cannot have a generic method like this in C#, it won't compile: class Bar { void GenericMethod(T arg) { arg.Foo(); } } Instead you need a constraint like this: interface IFoo { void Foo(); } class Bar { void GenericMethod(T arg) where T: IFoo { arg.Foo(); } } No you're wrong. There's no need for interfaces or for generic constraints. It's not static vs duck typing. It's just a method lookup issue. See for yourself: http://rextester.com/GFKNSK99121 Similarly for LINQ, you cannot just implement a generic "Sum" extension method for IEnumerable that works for all T, because you cannot just use the + operator in that method. It is not defined on T if there are no respective constraints. Look at how it is implemented separately for every type T that supports +: https://msdn.microsoft.com/de-de/library/system.linq.enumerable.sum(v=vs.110).aspx Sum is implemented in that stupid way, because unlike C++, in C# operators need to be implemented as static methods, so you can't abstract them with an interface. If they were instance methods, you could implement them outside of the class as extension methods and there would be no need to write a distinct method for each type. Here's an example: http://rextester.com/PQFPC46087 The only thing missing is syntax sugar to forward the '+' operator to 'Add' in my example. I'm guessing that operator overloading was designed that way because: 1) they're worried about boxing and virtual call overhead 2) operator overloading was designed before generics (IIRC).
Re: ADL
On 9/3/16 5:57 PM, Manu via Digitalmars-d wrote: On 3 September 2016 at 22:42, Andrei Alexandrescu via Digitalmars-d <digitalmars-d@puremagic.com> wrote: On 9/3/16 1:51 AM, Manu via Digitalmars-d wrote: I've never thought about this problem in C++, or had any problems with ADL. How do you swap two objects of a generic type that may or may not define its own swap? -- Andrei It's not a problem I've ever had. A problem you didn't know you have. It's a classic C++ conundrum combining theory and practice. I'm not actually quite sure I understand your question... The task is to swap two objects of generic type T. If T's namespace defines an overload of swap with the appropriate signature, use it. Otherwise, fall back to std::swap. For bonus points: if T defines swap as a _member_ function, use that. I guess this is sort of like the issue I was alluding to in my other thread though; there exists a generic implementation, but some type requires to specialise for itself, which then needs to trump the otherwise ambiguous conflict with the catch-all? I think that's an interesting problem, but it's quite easy to solve with an (ugly) forwarding template; but that forwawrding template leads right back here, where the worker `doThingImpl()` as implemented for each type that supports 'doThing' depends on ADL to be callable from the master `doThing()` function. Post the code. Andrei
Re: ADL
On 3 September 2016 at 23:04, Andrei Alexandrescu via Digitalmars-dwrote: > On 9/3/16 1:24 PM, Walter Bright wrote: >> >> On 9/3/2016 3:12 AM, Walter Bright wrote: >>> >>> If you are still determined to use it, you can use: >>> >>>__traits(compiles, ...) >>> >>> like you would SFINAE in C++ to select which of the modules from the >>> argument >>> types selects a function that compiles. >> >> >> Eh, I realized it's simpler than that. Based on the code I already >> presented, each argument can be used to generate an import for its >> corresponding version of the function. Then, overloading rules apply and >> it works. Something like: >> >> Something like: >> >> void foo(T,U)(T t, U u) >> { >> alias func = ModuleOf!T.func; >> alias func = ModuleOf!U.func; >> >> func(t, u); >> } > > > This only works with the respective modules do define `func`. We need > something that conditionally plants the symbol depending on whether the > module defines it or not. -- Andrei Right, and it also has to not conflict with possible local definitions, or instances supplied by imports in the local namespace. Ie, the module where T came from is *an additional* place to look, not *the* place to look. I expect that local definitions may exist for things like generic fallbacks, or primitive/builtin type implementations.
Re: ADL
On 3 September 2016 at 22:42, Andrei Alexandrescu via Digitalmars-d <digitalmars-d@puremagic.com> wrote: > On 9/3/16 1:51 AM, Manu via Digitalmars-d wrote: >> >> I've >> never thought about this problem in C++, or had any problems with ADL. > > > How do you swap two objects of a generic type that may or may not define its > own swap? -- Andrei It's not a problem I've ever had. I'm not actually quite sure I understand your question... I guess this is sort of like the issue I was alluding to in my other thread though; there exists a generic implementation, but some type requires to specialise for itself, which then needs to trump the otherwise ambiguous conflict with the catch-all? I think that's an interesting problem, but it's quite easy to solve with an (ugly) forwarding template; but that forwawrding template leads right back here, where the worker `doThingImpl()` as implemented for each type that supports 'doThing' depends on ADL to be callable from the master `doThing()` function.
Re: ADL
On 3 September 2016 at 21:16, Walter Bright via Digitalmars-d <digitalmars-d@puremagic.com> wrote: > On 9/3/2016 3:51 AM, Timon Gehr wrote: >> >> By default, name lookup does not work in a way that would allow you to >> actually >> extend types using UFCS, and therefore none of Phobos works that way. > > > Lambdas! This is exactly the difference between std.algorithm, and what I was trying to express as an algorithm that 'does work'. It's not the business of the API for the user to supply the work to do (ie, via lambda); the function is meant to do the work, which means it needs to call other functions. There are no lambdas to be seen in this situation. > (Besides, I showed how other scopes can be imported based on a type, and > then things can be looked up in those scopes, and UFCS applied.) I still think that's unnecessarily complicated, and multiple arguments leads to static if-ing and __traits(compiles,...). The fact the user needs to intervene at all is already too much. Apparently I need to stress again, this is a *core value proposition of D*... It's presented as "this is modern D code", and yet it's awkward and requires careful handling or you get hard to understand name-resolution issues. UFCS *is* modern D. Algorithms and ranges *is* modern D. Seriously, this is the style that modern D aspires to, and it doesn't 'just work'. There should be ear-piercing alarms and flashing red everywhere. This comes up for me frequently, yet ADL has never caused me a single moments trouble, in 15+ years. I didn't even know ADL existed until I started running into this problem in D and then wondered to myself why I never encountered the same issue in C++. It worked so seamlessly and intuitively, I didn't even know it was there. I don't care if the solution is ADL like C++, or something else that works, just that this problem is real; it's a massive fly in the ointment of modern D style, and I don't think it's acceptable. It needs a seamless solution, not manual intervention at every case. D depends on this so much more than C++ does.
Re: ADL
On 3 September 2016 at 21:24, Walter Bright via Digitalmars-dwrote: > On 9/3/2016 3:12 AM, Walter Bright wrote: >> >> If you are still determined to use it, you can use: >> >>__traits(compiles, ...) >> >> like you would SFINAE in C++ to select which of the modules from the >> argument >> types selects a function that compiles. > > > Eh, I realized it's simpler than that. Based on the code I already > presented, each argument can be used to generate an import for its > corresponding version of the function. Then, overloading rules apply and it > works. Something like: > > Something like: > > void foo(T,U)(T t, U u) > { > alias func = ModuleOf!T.func; > alias func = ModuleOf!U.func; > > func(t, u); > } And if either module doesn't have an instance of func?
Re: ADL
On Saturday, 3 September 2016 at 12:25:11 UTC, Andrei Alexandrescu wrote: What problems are you referring to? -- Andrei The problems discussed here in the thread related to name lookup at template instantiation time. And also similar problems related to visibility (public/private) that were discussed in a different thread recently.
Re: ADL
On Saturday, 3 September 2016 at 12:40:26 UTC, ZombineDev wrote: No, LINQ doesn't work because of interfaces, but because of extension methods (C#'s variant of UFCS). The IEnumerable interface defines only a single method. All the useful functionality is implemented as extension methods which are only available if the user specifically imports the namespace in which where they're defined (just like D's ranges and range primitive implementations for arrays). Those extension methods are used as a fallback, similarly to UFCS in D: every type can override the extension methods by implementing the method itself. Also more inner namespaces (more closer to the method invocation) override more outer namespaces. I know extension methods, that's not the point. The point is, that you cannot have a generic method like this in C#, it won't compile: class Bar { void GenericMethod(T arg) { arg.Foo(); } } Instead you need a constraint like this: interface IFoo { void Foo(); } class Bar { void GenericMethod(T arg) where T: IFoo { arg.Foo(); } } Similarly for LINQ, you cannot just implement a generic "Sum" extension method for IEnumerable that works for all T, because you cannot just use the + operator in that method. It is not defined on T if there are no respective constraints. Look at how it is implemented separately for every type T that supports +: https://msdn.microsoft.com/de-de/library/system.linq.enumerable.sum(v=vs.110).aspx
Re: ADL
On Saturday, 3 September 2016 at 10:11:05 UTC, Timon Gehr wrote: If ADL is done as a fallback, then it is only slower in those cases where it is either actually used, or __traits(compiles,...) is used to determine that some function overload does not exist. True. Still it does complicate the implementation. AFAICS the point of ADL is importing function definitions automatically if they are referenced, thereby practically circumventing the guarantees imports give you. In particular : "I will only import what is in that module, and I will only transitively import what is imported publicly by this module" now it becomes : "I will import what is in that module, transitively import public imports, and maybe more if a function called from that module requires it." Please do correct me if my interpretation is wrong. I haven't heard of adl before this post.
Re: ADL
On Saturday, 3 September 2016 at 10:33:22 UTC, Walter Bright wrote: On 9/3/2016 3:14 AM, Timon Gehr wrote: On 03.09.2016 10:37, Walter Bright wrote: None of the algorithms used in std.algorithm or elsewhere in Phobos have this particular issue. Yes they do. It is not possible to implement the range functions as non-members. It's done for arrays via std.array. Nor have I seen ADL supported in any other language, despite many supporting generic algorithms. Which other such languages have templates like D or C++? I don't think it is a template issue. It's a name lookup issue. There's LINQ in C#, for example. I agree that it's not a template issue. It's more of a modules vs namespaces issue. I think the lack of ADL is not a problem in C# because everyone can (and everyone does) extend an existing namespace, so most user's of LINQ algorithms just slap a `using System.Linq` on top of the file without caring in exactly which file the functionality they're using is coming from. Personally I'm glad that D doesn't have ADL because ADL can make it very hard to find which overload is called.
Re: ADL
On 2016-09-03 13:16, Walter Bright wrote: It's mostly about how templates specify what interface they require and how the requirements are satisfied by the caller. ADL is a workaround for the lack of a convenient enough such protocol in templates. Other approaches to generics solve this particular issue quite elegantly. (E.g. type classes implicitly pass along the required free-function functionality.) D's templates don't, this is why it is a template issue. By default, name lookup does not work in a way that would allow you to actually extend types using UFCS, and therefore none of Phobos works that way. Lambdas! So, something like this: module foo; struct Foo {} int front(Foo f); void popFront(Foo f); bool empty(Foo f); module algo; void algorithm(alias front, alias popFront, alias empty, T)(T t); module user; import foo; import algo; void main() { Foo f; algorithm!(() => f.front, () => f.popFront(), () => f.empty)(f); } -- /Jacob Carlborg
Re: ADL
On 9/3/16 1:24 PM, Walter Bright wrote: On 9/3/2016 3:12 AM, Walter Bright wrote: If you are still determined to use it, you can use: __traits(compiles, ...) like you would SFINAE in C++ to select which of the modules from the argument types selects a function that compiles. Eh, I realized it's simpler than that. Based on the code I already presented, each argument can be used to generate an import for its corresponding version of the function. Then, overloading rules apply and it works. Something like: Something like: void foo(T,U)(T t, U u) { alias func = ModuleOf!T.func; alias func = ModuleOf!U.func; func(t, u); } This only works with the respective modules do define `func`. We need something that conditionally plants the symbol depending on whether the module defines it or not. -- Andrei
Re: ADL
On 9/3/16 12:14 PM, Timon Gehr wrote: On 03.09.2016 10:37, Walter Bright wrote: None of the algorithms used in std.algorithm or elsewhere in Phobos have this particular issue. Yes they do. It is not possible to implement the range functions as non-members. Yah, but I don't see this as an issue. The non-members would need to be in the same module even with ADL, so it's just a clerical matter. -- Andrei
Re: ADL
On 9/3/16 12:03 PM, Timon Gehr wrote: On 03.09.2016 11:37, Manu via Digitalmars-d wrote: On 3 September 2016 at 18:56, Walter Bright via Digitalmars-d <digitalmars-d@puremagic.com> wrote: On 9/3/2016 1:37 AM, Walter Bright wrote: I thought #4 in particular was rather cool, I plan to use it as an example. https://github.com/dlang/phobos/pull/4762 Complexity ramps up further if there are N arguments to the algorithm. It needs to search each of the arguments modules. template adl(string fun){ /* TODO */ } adl!"foo"(S.init,T.init); Nice, yah, that kinds of stuff. -- Andrei
Re: ADL
On 9/3/16 3:09 AM, Walter Bright wrote: [snip] What would be really nice is to allow ADL easily and without fuss when needed. On Manu's example: module bob; struct S {} void f(S s); module joe; struct T {} void f(T t); module myalgorithm; void test(T)(T t) { mixin(adl!(T, "f")); f(t); } So adl!(T, "f") expands to an import of f from T's module if it defines a function f, or nothing if it doesn't. Generally I agree that there's more upside to not introducing ADL for D. Andrei
Re: ADL
On 9/3/16 1:51 AM, Manu via Digitalmars-d wrote: I've never thought about this problem in C++, or had any problems with ADL. How do you swap two objects of a generic type that may or may not define its own swap? -- Andrei
Re: ADL
On Saturday, 3 September 2016 at 10:56:20 UTC, Tobias M wrote: On Saturday, 3 September 2016 at 10:33:22 UTC, Walter Bright wrote: I don't think it is a template issue. It's a name lookup issue. There's LINQ in C#, for example. I think it is. The problem is lookup of dependent symbols (see C++ two phase lookup). Without real templates, all lookup can be done at definition time. I'm not very familiar with LINQ, but generally C# uses uses interfaces as constraints on generics, similar to traits/type classes. Lookup is then done once, considering only the interfaces, not for each the concrete type. No, LINQ doesn't work because of interfaces, but because of extension methods (C#'s variant of UFCS). The IEnumerable interface defines only a single method. All the useful functionality is implemented as extension methods which are only available if the user specifically imports the namespace in which where they're defined (just like D's ranges and range primitive implementations for arrays). Those extension methods are used as a fallback, similarly to UFCS in D: every type can override the extension methods by implementing the method itself. Also more inner namespaces (more closer to the method invocation) override more outer namespaces. For more info see: 1) https://github.com/ljw1004/csharpspec/blob/gh-pages/expressions.md#member-lookup Member Lookup 2) https://github.com/ljw1004/csharpspec/blob/gh-pages/expressions.md#member-access Member access and 3) https://github.com/ljw1004/csharpspec/blob/gh-pages/expressions.md#extension-method-invocations Extension method invocations
Re: ADL
On 9/3/16 10:01 AM, Tobias Müller wrote: On Friday, 2 September 2016 at 23:51:35 UTC, Manu wrote: This pattern seems to bite me every direction I turn when trying to write range or algorithm style code. C++ has ADL, and ADL works. I've never thought about this problem in C++, or had any problems with ADL. IMO the root of this problem is that templates are *duck typed*. All those problems wouldn't even exist with concepts/traits/typeclasses (done right). ADL is only an ugly hack. And with "Design by Introspection" it only gets worse: If an optional operation exists, but is not found because of unexpected problems like these, it still compiles but you only get limited functionality or bad performance. What problems are you referring to? -- Andrei
Re: ADL
On 9/3/2016 3:12 AM, Walter Bright wrote: If you are still determined to use it, you can use: __traits(compiles, ...) like you would SFINAE in C++ to select which of the modules from the argument types selects a function that compiles. Eh, I realized it's simpler than that. Based on the code I already presented, each argument can be used to generate an import for its corresponding version of the function. Then, overloading rules apply and it works. Something like: Something like: void foo(T,U)(T t, U u) { alias func = ModuleOf!T.func; alias func = ModuleOf!U.func; func(t, u); }
Re: ADL
On 9/3/2016 3:51 AM, Timon Gehr wrote: I don't think it is a template issue. It's a name lookup issue. It's both. ADL is mostly useless outside of generic code. It was initially justified as a solution for operator overloading, which has no necessary relationship to templates or generic programming. It's mostly about how templates specify what interface they require and how the requirements are satisfied by the caller. ADL is a workaround for the lack of a convenient enough such protocol in templates. Other approaches to generics solve this particular issue quite elegantly. (E.g. type classes implicitly pass along the required free-function functionality.) D's templates don't, this is why it is a template issue. By default, name lookup does not work in a way that would allow you to actually extend types using UFCS, and therefore none of Phobos works that way. Lambdas! (Besides, I showed how other scopes can be imported based on a type, and then things can be looked up in those scopes, and UFCS applied.) There's LINQ in C#, for example. C# does not have templates. It has generics: https://msdn.microsoft.com/en-us/library/512aeb7t.aspx and iterators and algorithms and LINQ.
Re: ADL
On 9/3/2016 2:31 AM, Manu via Digitalmars-d wrote: I'm tired of these sorts of dismissals. You insist that I'm not a 'real' D programmer, or something to that effect. Not at all. No insult was intended. People often find better, more D idiomatic ways of writing code and tell me I need to change my style to use it. For example, there was a long discussion about the right way to use @trusted in templates a while back. The way I had considered "best practice" was inferior, and I simply could no longer defend it and adopted the way others had developed. Just because I have been writing D longer than anyone doesn't mean I automatically am imbued with wisdom of the best way to do things. We're all learning. The way I write D code constantly evolves. If we're going to find the best way to do things in D, we're going to have to be willing to check our pride at the door and be willing to reexamine any assumption and practice, no matter how long it has been in use. My purpose in participating in this thread is to help you be successful using D, not denigrate you. I'm fully aware that I'm often rather artless in the way I make points, social skills are one of my challenges. So I ask you to be tolerant of my shortcomings in that area, and try to look past it.
Re: ADL
On Saturday, 3 September 2016 at 10:33:22 UTC, Walter Bright wrote: I don't think it is a template issue. It's a name lookup issue. There's LINQ in C#, for example. I think it is. The problem is lookup of dependent symbols (see C++ two phase lookup). Without real templates, all lookup can be done at definition time. I'm not very familiar with LINQ, but generally C# uses uses interfaces as constraints on generics, similar to traits/type classes. Lookup is then done once, considering only the interfaces, not for each the concrete type.
Re: ADL
On 03.09.2016 12:33, Walter Bright wrote: On 9/3/2016 3:14 AM, Timon Gehr wrote: On 03.09.2016 10:37, Walter Bright wrote: None of the algorithms used in std.algorithm or elsewhere in Phobos have this particular issue. Yes they do. It is not possible to implement the range functions as non-members. It's done for arrays via std.array. ... This is not at all relevant when talking about 'this particular issue' that Manu brought up. std.range and std.algorithm import std.array. Nor have I seen ADL supported in any other language, despite many supporting generic algorithms. Which other such languages have templates like D or C++? I don't think it is a template issue. It's a name lookup issue. It's both. ADL is mostly useless outside of generic code. It's mostly about how templates specify what interface they require and how the requirements are satisfied by the caller. ADL is a workaround for the lack of a convenient enough such protocol in templates. Other approaches to generics solve this particular issue quite elegantly. (E.g. type classes implicitly pass along the required free-function functionality.) D's templates don't, this is why it is a template issue. By default, name lookup does not work in a way that would allow you to actually extend types using UFCS, and therefore none of Phobos works that way. Note that I'm not saying ADL should be implemented, but the problem it addresses is real, and it exists in D. There's LINQ in C#, for example. C# does not have templates.
Re: ADL
On 9/3/2016 2:31 AM, Manu via Digitalmars-d wrote: std.algorithm is extremely simple, That's actually a nice compliment! (Though credit for that goes to Andrei, not me.) it doesn't do anything except raw algorithm-ey stuff. It doesn't attempt to invoke functionality on the data it's working on. Sure it does, usually via a lambda passed to it. Recall that C++ ADL predates C++ lambdas by more than a decade, which may explain why C++ has come to rely on ADL.
Re: ADL
On 9/3/2016 3:11 AM, Timon Gehr wrote: If ADL is done as a fallback, then it is only slower in those cases where it is either actually used, That isn't how it works in C++. It's done right up front in finding the candidates for overloading, not as a fallback. Given Manu's other posts where he wants a generic template to be used as the fallback, I don't think ADL as a fallback will work for him, either.
Re: ADL
On Saturday, 3 September 2016 at 09:31:59 UTC, Manu wrote: std.algorithm is extremely simple, it doesn't do anything except raw algorithm-ey stuff. It doesn't attempt to invoke functionality on the data it's working on. Right now I'm working on image processing. There are lots of image data types, and they all have things like interpolation and blending functions. Write an image processing algorithm that calls out to lerp or blend, and you'll run into these problems instantly. I was writing some audio software some time back, again, trying to use stream processing extensively because it's a perfect match for that workload, but same problem! Write an algorithm that does _work_, rather than does algorithm logic, and you can't miss this problem. You need to call associated functions to do work. I have had problems with not having C++ style ADL before, but in the end I'm much happier without it. At the risk of repeating previous posts, Is it specifically this? auto someAlgorithm(T0, T1)(T0 arg0, T1 arg1) { // do some work blend(arg0, arg1); // more work } And you want some way for the author of the types being passed in to define an overload of blend somewhere else that that someAlgorithm is not explicitly aware of? What possible lookup rules would you want to make that work? I can think of a lot of different schemes depending on what you want/need for the situation. D goes the simple, conservative route by default and just looks in progressively wider scopes, which is uncontroversial and good enough in most cases. More complicated cases can be done with various other methods, all of which will then necessarily involve some explicit choice (e.g. passing the scope, passing the function, importing scope etc...) in order to know which one you're using. This seems to me to be a good thing? P.s. your ordinary programmer argument: can you imagine that same ordinary programmer understanding how to properly use and avoid abusing C++ lookup rules?
Re: ADL
On 9/3/2016 3:14 AM, Timon Gehr wrote: On 03.09.2016 10:37, Walter Bright wrote: None of the algorithms used in std.algorithm or elsewhere in Phobos have this particular issue. Yes they do. It is not possible to implement the range functions as non-members. It's done for arrays via std.array. Nor have I seen ADL supported in any other language, despite many supporting generic algorithms. Which other such languages have templates like D or C++? I don't think it is a template issue. It's a name lookup issue. There's LINQ in C#, for example.
Re: ADL
On 9/3/2016 2:31 AM, Manu via Digitalmars-d wrote: Fourth solution: module myalgorithm; void test(T)(T t) { import std.traits; mixin("import " ~ std.traits.moduleName!T ~ ";"); mixin("alias M = " ~ std.traits.moduleName!T ~ ";"); // The above could be encapsulated into an eponymous template // that takes T as a parameter and returns the alias M.f(t); } What makes them problematic or highly unsavory? I thought #4 in particular was rather cool, I plan to use it as an example. I also had this idea as workaround, but you can't seriously think this is okay? Importing an entire module at the point I want to call a function is crazy. I don't want to import _everything_ from T's module into my local namespace; that could easily lead to conflicting names in the local scope which would now require disambiguation. This surely represents a far higher probability of name collisions than the theoretical accidental collision that could come from ADL. The ADL style collision isn't accidental though, that's _the whole point_. // Find module in which T was defined template ModuleOf(alias T) { import std.traits : moduleName; mixin("import " ~ moduleName!T ~ ";"); mixin("alias ModuleOf = " ~ moduleName!T ~ ";"); } The import is scoped inside ModuleOf, and so doesn't cause collisions. Besides, import foo : bar; only imports the symbol 'bar' from 'foo', no matter how many symbols there are in 'foo'. Write an algorithm that does _work_, rather than does algorithm logic, and you can't miss this problem. You need to call associated functions to do work. Why does no other language adopt ADL? ADL has been around in C++ for 25 years at least. Or maybe I missed that it does exist in other languages? (The usual way I've seen associated functions made available to generic algorithms is via alias parameters or lambdas that enclose the calls to those functions.)
Re: ADL
On 03.09.2016 10:37, Walter Bright wrote: None of the algorithms used in std.algorithm or elsewhere in Phobos have this particular issue. Yes they do. It is not possible to implement the range functions as non-members. Nor have I seen ADL supported in any other language, despite many supporting generic algorithms. Which other such languages have templates like D or C++?
Re: ADL
On 9/3/2016 2:37 AM, Manu via Digitalmars-d wrote: On 3 September 2016 at 18:56, Walter Bright via Digitalmars-d <digitalmars-d@puremagic.com> wrote: On 9/3/2016 1:37 AM, Walter Bright wrote: I thought #4 in particular was rather cool, I plan to use it as an example. https://github.com/dlang/phobos/pull/4762 > Complexity ramps up further if there are N arguments to the algorithm. > It needs to search each of the arguments modules. I suggest posting the actual problems you're having, because twice now you've gotten solutions to the problems you posted, then said they weren't your actual problems. This template: // Find module in which T was defined template ModuleOf(alias T) { import std.traits : moduleName; mixin("import " ~ moduleName!T ~ ";"); mixin("alias ModuleOf = " ~ moduleName!T ~ ";"); } can be used to qualify any function with the module in which one expects to find it. > Complexity ramps up further if there are N arguments to the algorithm. > It needs to search each of the arguments modules. Bluntly, if a library is designed around multi-argument ADL as a core requirement, redesign it. I.e. the same advice as for multiple inheritance. It's just not worth it. If you are still determined to use it, you can use: __traits(compiles, ...) like you would SFINAE in C++ to select which of the modules from the argument types selects a function that compiles.
Re: ADL
On 03.09.2016 03:12, Stefan Koch wrote: On Saturday, 3 September 2016 at 01:09:18 UTC, Walter Bright wrote: Essentially, ADL has awkward problems when getting beyond the simple cases. It isn't right for D. I could not agree more strongly! If this feature were supported, it would probably break our module system. Break how? Even if we could shoehorn it into the language it would make the compiler slower. If ADL is done as a fallback, then it is only slower in those cases where it is either actually used, or __traits(compiles,...) is used to determine that some function overload does not exist.
Re: ADL
On 03.09.2016 11:37, Manu via Digitalmars-d wrote: On 3 September 2016 at 18:56, Walter Bright via Digitalmars-d <digitalmars-d@puremagic.com> wrote: On 9/3/2016 1:37 AM, Walter Bright wrote: I thought #4 in particular was rather cool, I plan to use it as an example. https://github.com/dlang/phobos/pull/4762 Complexity ramps up further if there are N arguments to the algorithm. It needs to search each of the arguments modules. template adl(string fun){ /* TODO */ } adl!"foo"(S.init,T.init);
Re: ADL
On 3 September 2016 at 18:56, Walter Bright via Digitalmars-dwrote: > On 9/3/2016 1:37 AM, Walter Bright wrote: >> >> I thought #4 in particular was rather cool, I plan to use it as an >> example. > > > https://github.com/dlang/phobos/pull/4762 Complexity ramps up further if there are N arguments to the algorithm. It needs to search each of the arguments modules.
Re: ADL
On 3 September 2016 at 11:09, Walter Bright via Digitalmars-d <digitalmars-d@puremagic.com> wrote: > > First solution: > >module bob; >struct S { >void f(); >} This is my current workaround. I'm not happy with it at all. UFCS exists for a reason. > Second solution: > > module user_code; > import bob, joe; > import myalgorithm; > > mixin myalgorithm.test!S; > mixin myalgorithm.test!T; > > void main() > { > test(S.init); > test(T.init); > } You're not serious, right? I just want to call a function... functions should be functions, not mixin templates! > Third solution: > > module myalgorithm; > void test(M,T)(T t) > { > M.f(t); > } > > module user_code; > import bob, joe; > import myalgorithm; > > void main() > { > test!bob(S.init); > test!joe(T.init); > } Another crazy workaround. Users should not be expected to manually pass scope's around the place to perform a name lookup. Try and explain that to a normal programmer. > Fourth solution: > > module myalgorithm; > > void test(T)(T t) > { > import std.traits; > mixin("import " ~ std.traits.moduleName!T ~ ";"); > mixin("alias M = " ~ std.traits.moduleName!T ~ ";"); > // The above could be encapsulated into an eponymous template > // that takes T as a parameter and returns the alias > > M.f(t); > } > > What makes them problematic or highly unsavory? I thought #4 in particular > was rather cool, I plan to use it as an example. I also had this idea as workaround, but you can't seriously think this is okay? Importing an entire module at the point I want to call a function is crazy. I don't want to import _everything_ from T's module into my local namespace; that could easily lead to conflicting names in the local scope which would now require disambiguation. This surely represents a far higher probability of name collisions than the theoretical accidental collision that could come from ADL. The ADL style collision isn't accidental though, that's _the whole point_. >> or had any problems with ADL > > https://en.wikipedia.org/wiki/Argument-dependent_name_lookup#Criticism > > Essentially, ADL has awkward problems when getting beyond the simple cases. > It isn't right for D. D requires ADL so much more than C++ does, because the things ADL does in C++ are absolute concrete advertised core value propositions of D. > ADL has the problems I provided a link to. It's never caused me a problem, in like, 15 years or more. This situation in D causes me problems all the time. > In any case, these difficulties are the consequence of trying to write C++ > code in D. You've told me this before, and I find it kind of offensive every time you do, since I've been programming D for like 7 years now, and I definitely don't code D like C++. If anything, I have a strong tendency to code C++ like D, and that has lead to a lot of interesting changes in my C++ style. I'm tired of these sorts of dismissals. You insist that I'm not a 'real' D programmer, or something to that effect. This problem consistently arises when I try to commit to go all in on ranges+UFCS pipeline style programming. There's nothing C++-ey about that. C++ can barely do it. It is the advertised mission of modern D programmers to write code this way, and it's the exact area where the problem I'm trying to express breaks down. The interesting part is, when I do try and code this way in C++ (which is brutal, it's all SFINAE based template constraints and stuff), it actually _works_, simply because ADL works. Pipeline programming is a core value proposition for D, as are uber-powerful templates which leads to making algorithms out of everything. > None of the algorithms used in std.algorithm or elsewhere in > Phobos have this particular issue. Nor have I seen ADL supported in any > other language, despite many supporting generic algorithms. std.algorithm is extremely simple, it doesn't do anything except raw algorithm-ey stuff. It doesn't attempt to invoke functionality on the data it's working on. Right now I'm working on image processing. There are lots of image data types, and they all have things like interpolation and blending functions. Write an image processing algorithm that calls out to lerp or blend, and you'll run into these problems instantly. I was writing some audio software some time back, again, trying to use stream processing extensively because it's a perfect match for that workload, but same problem! Write an algorithm that does _work_, rather than does algorithm logic, and you can't miss this problem. You need to call associated functions t
Re: ADL
On 9/3/2016 1:37 AM, Walter Bright wrote: I thought #4 in particular was rather cool, I plan to use it as an example. https://github.com/dlang/phobos/pull/4762
Re: ADL
On 9/2/2016 9:14 PM, Manu via Digitalmars-d wrote: They're not solutions though, they're workarounds. They're all problematic, and highly unsavoury. What makes them problematic or highly unsavory? I thought #4 in particular was rather cool, I plan to use it as an example. Nobody is gonna go "oh, i really wish i could do those things in C++!", because the problem is already solved :/ ADL has the problems I provided a link to. In any case, these difficulties are the consequence of trying to write C++ code in D. None of the algorithms used in std.algorithm or elsewhere in Phobos have this particular issue. Nor have I seen ADL supported in any other language, despite many supporting generic algorithms. I do understand trying to write C++ code in D, because my early FORTRAN programs looked like BASIC, my early C programs looked like FORTRAN, my C++ code looked like C, etc. What I have provided is a generic way to make ADL work in D, which shows how adaptable it is.
Re: ADL
On Friday, 2 September 2016 at 23:51:35 UTC, Manu wrote: This pattern seems to bite me every direction I turn when trying to write range or algorithm style code. C++ has ADL, and ADL works. I've never thought about this problem in C++, or had any problems with ADL. IMO the root of this problem is that templates are *duck typed*. All those problems wouldn't even exist with concepts/traits/typeclasses (done right). ADL is only an ugly hack. And with "Design by Introspection" it only gets worse: If an optional operation exists, but is not found because of unexpected problems like these, it still compiles but you only get limited functionality or bad performance.
Re: ADL
On 3 September 2016 at 11:25, Walter Bright via Digitalmars-d <digitalmars-d@puremagic.com> wrote: > On 9/2/2016 6:12 PM, Stefan Koch wrote: >> >> If this feature were supported, it would probably break our module system. >> Even if we could shoehorn it into the language it would make the compiler >> slower. > > > Note that C++ needs ADL in part because it cannot do options 2, 3 or 4. They're not solutions though, they're workarounds. They're all problematic, and highly unsavoury. Nobody is gonna go "oh, i really wish i could do those things in C++!", because the problem is already solved :/
Re: ADL
On 9/2/2016 6:12 PM, Stefan Koch wrote: If this feature were supported, it would probably break our module system. Even if we could shoehorn it into the language it would make the compiler slower. Note that C++ needs ADL in part because it cannot do options 2, 3 or 4.
Re: ADL
On Saturday, 3 September 2016 at 01:09:18 UTC, Walter Bright wrote: Essentially, ADL has awkward problems when getting beyond the simple cases. It isn't right for D. I could not agree more strongly! If this feature were supported, it would probably break our module system. Even if we could shoehorn it into the language it would make the compiler slower.
Re: ADL
On 9/2/2016 4:51 PM, Manu via Digitalmars-d wrote: (I should have given the example with test() outside the namespace) It's always best to provide an example of the actual problem rather than something else. module bob; struct S {} void f(S s); module joe; struct T {} void f(T t); module myalgorithm; void test(T)(T t) { f(t); } module user_code; import bob, joe; import myalgorithm; // needed void main() { test(S.init); test(T.init); } This is a better example. I can't be invading test() with any aliases, or imports. It wouldn't be an algorithm anymore if I did that. This pattern seems to bite me every direction I turn when trying to write range or algorithm style code. C++ has ADL, and ADL works. I've never thought about this problem in C++, First solution: module bob; struct S { void f(); } Second solution: module user_code; import bob, joe; import myalgorithm; mixin myalgorithm.test!S; mixin myalgorithm.test!T; void main() { test(S.init); test(T.init); } Third solution: module myalgorithm; void test(M,T)(T t) { M.f(t); } module user_code; import bob, joe; import myalgorithm; void main() { test!bob(S.init); test!joe(T.init); } Fourth solution: module myalgorithm; void test(T)(T t) { import std.traits; mixin("import " ~ std.traits.moduleName!T ~ ";"); mixin("alias M = " ~ std.traits.moduleName!T ~ ";"); // The above could be encapsulated into an eponymous template // that takes T as a parameter and returns the alias M.f(t); } > or had any problems with ADL https://en.wikipedia.org/wiki/Argument-dependent_name_lookup#Criticism Essentially, ADL has awkward problems when getting beyond the simple cases. It isn't right for D.
Re: ADL
On 3 September 2016 at 08:38, Walter Bright via Digitalmars-d <digitalmars-d@puremagic.com> wrote: > On 9/2/2016 5:15 AM, Manu via Digitalmars-d wrote: >> >> In C++, there is this ADL thing (argument dependent lookup). > > > Yeah, I know about Koening lookup. It was a hack added to C++ to make > operator overloading work. Naturally you do, and I'm sure that is why it was invented, but you couldn't write any modern C++ without it. The reason you say it was invented is not the reason that it's useful. >> D doesn't seem to have this, > > > That's right, and it's on purpose :-) And that seems to be a rather big problem. The situation is this: In D, it is ***EXTREMELY*** common to have some argument of type T, like, basically everything in D is a template these days... we're talking ranges and stuff. It is also considered un-cool in modern D to aggregate all functionality into types themselves. We want functionality for T to be extensible, so we use UFCS all over the place. Template args combined with UFCS practically demand ADL or something similar to ADL, otherwise you can't really write algorithms. It's impossible to import all the appropriate sources into the file that implements the algorithm. They're unrelataed, except that the algorithm is expected to 'work' on the T it's given. So if someone supplies a T to your algorithm, and it's a range for instance (or something following that pattern), and some part of it's implementation is UFCS, it all falls apart :/ We can't have the situation "UFCS works quite nicely... in this particular subset of common situations". >> and that is proving to be quite problematic. What's the work around? > > > Not a workaround, as D does not need ADL. This is how to do it: > > extern (C++, bob) { > struct S {} > void f(S s); > } > > extern (C++, joe) { > struct T {} > void f(T t); > > void test() > { > T t; > f(t); // obviously works, T is in the local namespace > > alias f = bob.f; // bring bob.s into current scope > bob.S s; > f(s); // no problemo > } > } > > > The 'alias' construct gives good control over which symbols are visible in > which scopes. Now to put it in the terms I describe above, 'test()' is an algorithm, implemented in a module unrelated to bob or joe (I should have given the example with test() outside the namespace)... the algorithm implementation can't go aliasing or importing anything relating to its possible arguments T or S; it's meant to be generic. module bob; struct S {} void f(S s); module joe; struct T {} void f(T t); module myalgorithm; void test(T)(T t) { f(t); } module user_code; import bob, joe; void main() { test(S.init); test(T.init); } This is a better example. I can't be invading test() with any aliases, or imports. It wouldn't be an algorithm anymore if I did that. This pattern seems to bite me every direction I turn when trying to write range or algorithm style code. C++ has ADL, and ADL works. I've never thought about this problem in C++, or had any problems with ADL.