www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - ADL

reply Manu via Digitalmars-d <digitalmars-d puremagic.com> writes:
In C++, there is this ADL thing (argument dependent lookup).
What it does is, when searching for overloads, in addition to looking
in the local namespace, it also looks in the namespace of the function
arguments.

D doesn't seem to have this, and that is proving to be quite problematic.
What's the work around?

C++ example:

namespace bob {
  struct S {};
  void f(S s);
}

namespace joe {
  struct T {};
  void f(T t);

  void test()
  {
    T t;
    f(t); // obviously works, T is in the local namespace

    bob::S s;
    f(s); // local namespace can't see `void f(S)`, but given the
argument 's', which is typed bob::S, it will search the bob::
namespace for overloads of f(), so this code compiles successfully.
  }
}


I have the same configuration across 2 modules in D.
In one module, I receive the foreign modules type via template arg,
but I haven't imported that type's module, so when I try to call the
function it can't find the overload, because it's not imported, and it
doesn't search the argument type's module (ie, namespace) for
overloads.
Sep 02 2016
next sibling parent David Nadlinger <code klickverbot.at> writes:
On Friday, 2 September 2016 at 12:15:25 UTC, Manu wrote:
 D doesn't seem to have this, and that is proving to be quite 
 problematic. What's the work around?
Somehow pass in the required information along with the symbol, for example as a member function/alias or an UDA. It's hard to get more specific than that without a concrete example. — David
Sep 02 2016
prev sibling next sibling parent Dicebot <public dicebot.lv> writes:
 protected-headers="v1"
From: Dicebot <public dicebot.lv>
Newsgroups: d,i,g,i,t,a,l,m,a,r,s,.,D
Subject: Re: ADL
References: <mailman.123.1472818535.2965.digitalmars-d puremagic.com>
In-Reply-To: <mailman.123.1472818535.2965.digitalmars-d puremagic.com>

--AXCi61NWPxphSaJ0W65PgQxEIkWE8JDpE
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable

On 09/02/2016 03:15 PM, Manu via Digitalmars-d wrote:
 In C++, there is this ADL thing (argument dependent lookup).
 What it does is, when searching for overloads, in addition to looking
 in the local namespace, it also looks in the namespace of the function
 arguments.
AFAIR it is intentionally not supported to simplify symbol lookup rules.
 I have the same configuration across 2 modules in D.
 In one module, I receive the foreign modules type via template arg,
 but I haven't imported that type's module, so when I try to call the
 function it can't find the overload, because it's not imported, and it
 doesn't search the argument type's module (ie, namespace) for
 overloads.
Don't know about best practice but I tend to simply find the host module for the argument and mixin the import for it inside the template to make symbols directly available. --AXCi61NWPxphSaJ0W65PgQxEIkWE8JDpE--
Sep 02 2016
prev sibling next sibling parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On 9/2/16 8:15 AM, Manu via Digitalmars-d wrote:
 In C++, there is this ADL thing (argument dependent lookup).
 What it does is, when searching for overloads, in addition to looking
 in the local namespace, it also looks in the namespace of the function
 arguments.

 D doesn't seem to have this, and that is proving to be quite problematic.
 What's the work around?

 C++ example:

 namespace bob {
   struct S {};
   void f(S s);
 }

 namespace joe {
   struct T {};
   void f(T t);

   void test()
   {
     T t;
     f(t); // obviously works, T is in the local namespace

     bob::S s;
     f(s); // local namespace can't see `void f(S)`, but given the
 argument 's', which is typed bob::S, it will search the bob::
 namespace for overloads of f(), so this code compiles successfully.
   }
 }


 I have the same configuration across 2 modules in D.
 In one module, I receive the foreign modules type via template arg,
 but I haven't imported that type's module, so when I try to call the
 function it can't find the overload, because it's not imported, and it
 doesn't search the argument type's module (ie, namespace) for
 overloads.
This is a limitation, you can only use struct members given a type, you can't use UFCS functions or regular calls like the above. You need to import the module in the template *definition* file, which is problematic. I think I remember seeing vibe.d do some funky shit to work around this, like getting the fully qualified name, and using string mixins to import the module that defines that type. I think it would be nice to see this fixed, but I'm not sure of the implications. One possibility: import __traits(moduleOf, T); Then you could do this import as a local import when you need it. Of course, this is not as nice as just having the compiler do this automatically. -Steve
Sep 02 2016
prev sibling next sibling parent Cauterite <cauterite gmail.com> writes:
On Friday, 2 September 2016 at 12:15:25 UTC, Manu wrote:

The only problem I have with this feature is that it would lead 
to implicit-importation, which is a totally foreign concept in D, 
and I would assume a design choice to not support it.

import bob : S;
S s;
// implicit `import bob : f;` for this statement:
f(s);

Is this an acceptable feature? Perhaps; my main concern is that 
it can be very difficult to work out where this `f` symbol is 
coming from. If you see the only import statement is `import bob 
: S;` then you'd naturally assume that the `f` being called here 
is not `bob.f`.

It's nice to learn this term "argument dependent lookup" though. 
I've spent a lot of time thinking about this feature, but never 
realised it had a name (and I never realised any languages 
supported it).
Sep 02 2016
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
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.
 D doesn't seem to have this,
That's right, and it's on purpose :-)
 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.
Sep 02 2016
parent reply Manu via Digitalmars-d <digitalmars-d puremagic.com> writes:
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.
Sep 02 2016
next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
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.
Sep 02 2016
next sibling parent reply Stefan Koch <uplink.coder googlemail.com> writes:
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.
Sep 02 2016
next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
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.
Sep 02 2016
parent reply Manu via Digitalmars-d <digitalmars-d puremagic.com> writes:
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 :/
Sep 02 2016
parent reply Walter Bright <newshound2 digitalmars.com> writes:
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.
Sep 03 2016
next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
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
Sep 03 2016
parent reply Manu via Digitalmars-d <digitalmars-d puremagic.com> writes:
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.
Sep 03 2016
next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
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);
Sep 03 2016
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Sep 03 2016
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
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.
Sep 03 2016
parent reply Walter Bright <newshound2 digitalmars.com> writes:
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); }
Sep 03 2016
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Sep 03 2016
next sibling parent reply Manu via Digitalmars-d <digitalmars-d puremagic.com> writes:
On 3 September 2016 at 23:04, Andrei Alexandrescu via Digitalmars-d
<digitalmars-d puremagic.com> 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
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.
Sep 03 2016
parent Walter Bright <newshound2 digitalmars.com> writes:
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.
Sep 03 2016
prev sibling next sibling parent reply vit <vit vit.vit> writes:
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")); } }
Sep 03 2016
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Sep 03 2016
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
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.
Sep 03 2016
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Sep 03 2016
prev sibling next sibling parent reply Manu via Digitalmars-d <digitalmars-d puremagic.com> writes:
On 3 September 2016 at 21:24, Walter Bright via Digitalmars-d
<digitalmars-d puremagic.com> 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); }
And if either module doesn't have an instance of func?
Sep 03 2016
parent reply Walter Bright <newshound2 digitalmars.com> writes:
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;
Sep 03 2016
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Sep 03 2016
parent reply Walter Bright <newshound2 digitalmars.com> writes:
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
Sep 03 2016
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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."
Sep 03 2016
parent reply Walter Bright <newshound2 digitalmars.com> writes:
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);
Sep 03 2016
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Sep 04 2016
parent Walter Bright <newshound2 digitalmars.com> writes:
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.
Sep 04 2016
prev sibling next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
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.
Sep 03 2016
next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
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(); }
Sep 03 2016
parent reply ZombineDev <petar.p.kirov gmail.com> writes:
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.
Sep 03 2016
parent Walter Bright <newshound2 digitalmars.com> writes:
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.
Sep 04 2016
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Sep 04 2016
parent reply Walter Bright <newshound2 digitalmars.com> writes:
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.
Sep 04 2016
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
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.
Sep 04 2016
parent reply Walter Bright <newshound2 digitalmars.com> writes:
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.
Sep 04 2016
next sibling parent reply Manu via Digitalmars-d <digitalmars-d puremagic.com> writes:
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.
Sep 04 2016
next sibling parent Walter Bright <newshound2 digitalmars.com> writes:
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.
Sep 04 2016
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Sep 05 2016
next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Sep 05 2016
prev sibling next sibling parent reply Jacob Carlborg <doob me.com> writes:
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
Sep 05 2016
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Sep 05 2016
parent reply Jacob Carlborg <doob me.com> writes:
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
Sep 05 2016
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Sep 05 2016
parent reply Jacob Carlborg <doob me.com> writes:
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
Sep 05 2016
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Sep 05 2016
prev sibling parent reply Lodovico Giaretta <lodovico giaretart.net> writes:
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?
Sep 05 2016
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 9/5/16 11:39 AM, Lodovico Giaretta wrote:
 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.
ADL would not apply here because it looks up only names in the same module as the type.
 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?
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. To implement the range interface for a type, provide a wrapper. KISS. D is powerful and that has the danger of making people entitled to ask for clever solutions to any problem there is, but at some point it just comes down to write some code pulp. Andrei
Sep 05 2016
parent Walter Bright <newshound2 digitalmars.com> writes:
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.
Sep 05 2016
prev sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
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.
Sep 05 2016
prev sibling parent Marc =?UTF-8?B?U2Now7x0eg==?= <schuetzm gmx.net> writes:
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); }
Sep 05 2016
prev sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
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++?
Sep 03 2016
next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
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.
Sep 03 2016
next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
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.
Sep 03 2016
parent reply Walter Bright <newshound2 digitalmars.com> writes:
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.
Sep 03 2016
next sibling parent Jacob Carlborg <doob me.com> writes:
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
Sep 03 2016
prev sibling parent reply Manu via Digitalmars-d <digitalmars-d puremagic.com> writes:
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.
Sep 03 2016
parent Walter Bright <newshound2 digitalmars.com> writes:
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.
Sep 03 2016
prev sibling next sibling parent reply Tobias M <troplin bluewin.ch> writes:
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.
Sep 03 2016
parent reply ZombineDev <petar.p.kirov gmail.com> writes:
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<T> 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/expressi ns.md#member-lookup Member Lookup 2) https://github.com/ljw1004/csharpspec/blob/gh-pages/expressi ns.md#member-access Member access and 3) https://github.com/ljw1004/csharpspec/blob/gh-pages/expressions.md#extensio -method-invocations Extension method invocations
Sep 03 2016
parent reply Tobias M <troplin bluewin.ch> writes:
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<T> 
 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>(T arg) { arg.Foo(); } } Instead you need a constraint like this: interface IFoo { void Foo(); } class Bar { void GenericMethod<T>(T arg) where T: IFoo { arg.Foo(); } } Similarly for LINQ, you cannot just implement a generic "Sum" extension method for IEnumerable<T> 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
Sep 03 2016
parent reply ZombineDev <petar.p.kirov gmail.com> writes:
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<T> 
 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>(T arg) { arg.Foo(); } } Instead you need a constraint like this: interface IFoo { void Foo(); } class Bar { void GenericMethod<T>(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<T> 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).
Sep 03 2016
parent reply Tobias M <troplin bluewin.ch> writes:
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...
Sep 03 2016
next sibling parent =?UTF-8?Q?Tobias=20M=C3=BCller?= <troplin bluewin.ch> writes:
Tobias M <troplin bluewin.ch> wrote:
 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.
Sep 03 2016
prev sibling parent reply ZombineDev <petar.p.kirov gmail.com> writes:
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?
Sep 03 2016
parent =?UTF-8?Q?Tobias=20M=C3=BCller?= <troplin bluewin.ch> writes:
ZombineDev <petar.p.kirov 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.
Sep 03 2016
prev sibling parent reply ZombineDev <petar.p.kirov gmail.com> writes:
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.
Sep 03 2016
parent Walter Bright <newshound2 digitalmars.com> writes:
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.
Sep 03 2016
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Sep 03 2016
prev sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
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.
Sep 03 2016
next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
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.
Sep 03 2016
parent Walter Bright <newshound2 digitalmars.com> writes:
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.
Sep 03 2016
prev sibling parent Stefan Koch <uplink.coder googlemail.com> writes:
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.
Sep 03 2016
prev sibling next sibling parent reply Manu via Digitalmars-d <digitalmars-d puremagic.com> writes:
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 to do work.
 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.
Again, stop this. It's insulting, and it really pisses me off. I'm writing textbook modern D, which is code that you can't even express in C++.
 What I have provided is a generic way to make ADL work in D, which shows how
 adaptable it is.
I never said D wasn't adaptable and capable of madhax. I understand very well that D is powerful enough to find a possible work-around for basically anything. You wouldn't believe some of the hacks I'm responsible for! (mostly relating to ref! >_<) It's useful once in a while, but once some particular hax present themselves as rule rather than the excepsion, you find yourself effectively engaged in writing boilerplate. Just like C, but a different kind of boilerplate, and for my money; extremely more complex boilerplate than C/C++, which only experts can read and understand. I am absolutely not okay with that sort of madhax.
Sep 03 2016
next sibling parent Walter Bright <newshound2 digitalmars.com> writes:
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.)
Sep 03 2016
prev sibling next sibling parent John Colvin <john.loughran.colvin gmail.com> writes:
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?
Sep 03 2016
prev sibling next sibling parent Walter Bright <newshound2 digitalmars.com> writes:
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.
Sep 03 2016
prev sibling next sibling parent Walter Bright <newshound2 digitalmars.com> writes:
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.
Sep 03 2016
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Sep 03 2016
next sibling parent reply Manu via Digitalmars-d <digitalmars-d puremagic.com> writes:
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
 (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.
I understand these things, but D isn't C++ and modules aren't namespaces. I think something that might appear ADL-like would look different and be simpler in D. An algorithm that calls a function on some T it receives just wants to look near the T; UFCS functions will be there. What you're effectively saying is they introduced ADL as a hack because the language was impractical without it. I'm saying the same situation exists in D right now. I don't think something ADL-like would have the same extent of negative impact on D as it does in C++. Modules are well-defined, and 'looking beside T for UFCS functions' is a much narrower and more well defined search scope.
 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.
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.
Sep 04 2016
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
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.
Sep 05 2016
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Sep 05 2016
next sibling parent deadalnix <deadalnix gmail.com> writes:
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.
Sep 05 2016
prev sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
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.)
Sep 05 2016
parent Guillaume Boucher <guillaume.boucher.d gmail.com> writes:
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.)
Sep 06 2016
prev sibling parent Manu via Digitalmars-d <digitalmars-d puremagic.com> writes:
On 5 September 2016 at 14:05, Manu <turkeyman 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.
Sep 04 2016
prev sibling next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Sep 03 2016
prev sibling parent reply Ethan Watson <gooberman gmail.com> writes:
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?
Sep 04 2016
parent reply Walter Bright <newshound2 digitalmars.com> writes:
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.
Sep 04 2016
parent Ethan Watson <gooberman gmail.com> writes:
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/wggldyzrbwjboibinuwj 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.
Sep 05 2016
prev sibling next sibling parent reply Tobias =?UTF-8?B?TcO8bGxlcg==?= <troplin bluewin.ch> writes:
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.
Sep 03 2016
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Sep 03 2016
parent reply Tobias M <troplin bluewin.ch> writes:
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.
Sep 03 2016
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Sep 03 2016
parent reply Tobias M <troplin bluewin.ch> writes:
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.
Sep 03 2016
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Sep 03 2016
parent reply =?UTF-8?Q?Tobias=20M=C3=BCller?= <troplin bluewin.ch> writes:
Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:
 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)
Sep 03 2016
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 9/3/16 7:38 PM, Tobias Müller wrote:
 Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:
 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
Sep 03 2016
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Sep 03 2016
parent reply Manu via Digitalmars-d <digitalmars-d puremagic.com> writes:
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.
Sep 03 2016
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Sep 03 2016
parent Walter Bright <newshound2 digitalmars.com> writes:
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.
Sep 03 2016