Jump to page: 1 25  
Page
Thread overview
Overloading based on attributes - is it a good idea?
May 28, 2019
Jonathan Marler
May 28, 2019
Walter Bright
May 28, 2019
Jonathan Marler
May 28, 2019
Walter Bright
May 28, 2019
Jonathan Marler
May 28, 2019
Jonathan Marler
May 28, 2019
Walter Bright
May 28, 2019
Jonathan Marler
May 28, 2019
Walter Bright
May 28, 2019
Jonathan Marler
May 28, 2019
Jonathan Marler
May 29, 2019
Walter Bright
May 29, 2019
Jonathan Marler
May 29, 2019
aliak
May 29, 2019
Jonathan Marler
May 29, 2019
Jonathan Marler
May 29, 2019
Atila Neves
May 29, 2019
Jonathan Marler
May 28, 2019
aliak
May 28, 2019
Manu
May 28, 2019
KnightMare
May 28, 2019
user1234
May 28, 2019
Manu
May 28, 2019
Jonathan Marler
May 29, 2019
Kagamin
May 29, 2019
Kagamin
May 29, 2019
Stefan Koch
May 29, 2019
Mike Franklin
May 30, 2019
Manu
May 31, 2019
Manu
May 31, 2019
Manu
May 31, 2019
Simen Kjærås
May 30, 2019
Exil
May 30, 2019
Q. Schroll
Jun 01, 2019
Exil
Jun 01, 2019
Exil
Jun 02, 2019
Jonathan Marler
May 30, 2019
Kagamin
May 28, 2019
int fun(int) pure;
int fun(int);

pure int gun(int x)
{
   return fun(x);
}

This doesn't work, but on the face of it it's not ambiguous - the second overload of fun() would not compile anyway.

I was wondering whether allowing overloading on attributes in general would be a good idea. I suspect templates and attribute deduction make that difficult.
May 28, 2019
On Tuesday, 28 May 2019 at 16:08:38 UTC, Andrei Alexandrescu wrote:
> int fun(int) pure;
> int fun(int);
>
> pure int gun(int x)
> {
>    return fun(x);
> }
>
> This doesn't work, but on the face of it it's not ambiguous - the second overload of fun() would not compile anyway.
>
> I was wondering whether allowing overloading on attributes in general would be a good idea. I suspect templates and attribute deduction make that difficult.

I posed this question a few years ago but specifically for @nogc. Maybe it makes sense for some attributes but not for others?  I think there could be some good use cases for overloading on @nogc, which would allow you to choose a different implementation depending on whether the GC is being used or not.

May 28, 2019
On 5/28/2019 9:08 AM, Andrei Alexandrescu wrote:
> int fun(int) pure;
> int fun(int);
> 
> pure int gun(int x)
> {
>     return fun(x);
> }
> 
> This doesn't work, but on the face of it it's not ambiguous - the second overload of fun() would not compile anyway.
> 
> I was wondering whether allowing overloading on attributes in general would be a good idea. I suspect templates and attribute deduction make that difficult.

More than difficult. I suspect it's impossible in the general case. D does inference of types and attributes from the bottom up, but this would be bottom up and top down. It's easy to know what to do for trivial cases, but for multiple levels and choices, that's graph theory that would be very complex to find a unique solution, and if a unique solution cannot be found, imagine the problems with informing the user just why.

It's like overloading based on return value:

  long fun(int);
  int fun(int);

  int gun(int x)
  {
     return fun(x);
  }

It's ambiguous even though the first overload of fun() would give an error.

Another consideration:

    class A { int fun() pure; }
    class B : A { overload int fun(); }

Because of covariance/contravariance, B.fun doesn't need to have the pure attribute added, the pureness is inherited from A.fun.
May 28, 2019
On Tuesday, 28 May 2019 at 16:08:38 UTC, Andrei Alexandrescu wrote:
> int fun(int) pure;
> int fun(int);
>
> pure int gun(int x)
> {
>    return fun(x);
> }
>
> This doesn't work, but on the face of it it's not ambiguous - the second overload of fun() would not compile anyway.
>
> I was wondering whether allowing overloading on attributes in general would be a good idea. I suspect templates and attribute deduction make that difficult.

I'm afraid of non-intuitive or unexpected selection of overloads. Example during debugging someone realizes that "oh i see, this one is used actually".
May 28, 2019
On Tuesday, 28 May 2019 at 17:27:08 UTC, Walter Bright wrote:
> On 5/28/2019 9:08 AM, Andrei Alexandrescu wrote:
>> int fun(int) pure;
>> int fun(int);
>> 
>> pure int gun(int x)
>> {
>>     return fun(x);
>> }
>> 
>> This doesn't work, but on the face of it it's not ambiguous - the second overload of fun() would not compile anyway.
>> 
>> I was wondering whether allowing overloading on attributes in general would be a good idea. I suspect templates and attribute deduction make that difficult.
>
> More than difficult. I suspect it's impossible in the general case. D does inference of types and attributes from the bottom up, but this would be bottom up and top down. It's easy to know what to do for trivial cases, but for multiple levels and choices, that's graph theory that would be very complex to find a unique solution, and if a unique solution cannot be found, imagine the problems with informing the user just why.
>
> It's like overloading based on return value:
>
>   long fun(int);
>   int fun(int);
>
>   int gun(int x)
>   {
>      return fun(x);
>   }
>
> It's ambiguous even though the first overload of fun() would give an error.
>
> Another consideration:
>
>     class A { int fun() pure; }
>     class B : A { overload int fun(); }
>
> Because of covariance/contravariance, B.fun doesn't need to have the pure attribute added, the pureness is inherited from A.fun.

At first glance this does seem correct.

After reading Walter's response here, another thought came to mind that instead of attribute overloading, a template with static-if could also be used.

void foo()()
{
    static if (__traits(gcAllowedHere))
    {
        // the GC implementation
    }
    else
    {
        // the nogc implementation
    }
}

void bar1() @nogc
{
    foo(); // calls the nogc implementation
}
void bar2()
{
    foo(); // calls the GC implementation
}

And the same thing could be applied for purity with something like __traits(inpureAllowedHere).

Although, I think we could use existing traits for these, with an "isCompiles" or something.
May 28, 2019
On 5/28/2019 10:35 AM, Jonathan Marler wrote:
> After reading Walter's response here, another thought came to mind that instead of attribute overloading, a template with static-if could also be used.

@nogc attributes are often inferred. Such constructs would make it undecidable.
May 28, 2019
On Tuesday, 28 May 2019 at 18:07:47 UTC, Walter Bright wrote:
> On 5/28/2019 10:35 AM, Jonathan Marler wrote:
>> After reading Walter's response here, another thought came to mind that instead of attribute overloading, a template with static-if could also be used.
>
> @nogc attributes are often inferred. Such constructs would make it undecidable.

You're right it's undecidable inside the function, but I think it's decidable if you check it at the call site. So if we could pass that information to the template then we could use it.  Using a default template parameter could work, though, we'd likely need to modify __traits(compiles) to work in the callers context when used as a default template parameter, or create a new trait like __trait(compilesAtCallSite):


int main(string[] args)
{
    nogcExample();
    gcExample();
    return 0;
}

version (WorksToday)
{
    void nogcExample() @nogc
    {
        allocateString!(__traits(compiles, new Object))(100);
    }
    void gcExample()
    {
        allocateString!(__traits(compiles, new Object))(100);
    }
    auto allocateString(bool allowGC)(size_t size)
    {
        static if (allowGC)
        {
            return new char[size];
        }
        else
        {
            import core.stdc.stdlib : malloc;
            return (cast(char*)malloc(size))[0 .. size];
        }
    }
}
else version (UseCompilesAtCallSite)
{
    void nogcExample() @nogc
    {
        allocateString(100);
    }
    void gcExample()
    {
        allocateString(100);
    }
    auto allocateString(bool allowGC = __traits(compilesAtCallSite, new Object))(size_t size)
    {
        static if (allowGC)
        {
            return new char[size];
        }
        else
        {
            import core.stdc.stdlib : malloc;
            return (cast(char*)malloc(size))[0 .. size];
        }
    }
}

Then again maybe if the nogcExample/gcExample functions were also templates, then we wouldn't be able to infer whether or not gc was available...not sure on that one.

May 28, 2019
On Tuesday, 28 May 2019 at 19:49:40 UTC, Jonathan Marler wrote:
> On Tuesday, 28 May 2019 at 18:07:47 UTC, Walter Bright wrote:
>> On 5/28/2019 10:35 AM, Jonathan Marler wrote:
>>> After reading Walter's response here, another thought came to mind that instead of attribute overloading, a template with static-if could also be used.
>>
>> @nogc attributes are often inferred. Such constructs would make it undecidable.
>
> You're right it's undecidable inside the function, but I think it's decidable if you check it at the call site. So if we could pass that information to the template then we could use it.  Using a default template parameter could work, though, we'd likely need to modify __traits(compiles) to work in the callers context when used as a default template parameter, or create a new trait like __trait(compilesAtCallSite):
>
>
> int main(string[] args)
> {
>     nogcExample();
>     gcExample();
>     return 0;
> }
>
> version (WorksToday)
> {
>     void nogcExample() @nogc
>     {
>         allocateString!(__traits(compiles, new Object))(100);
>     }
>     void gcExample()
>     {
>         allocateString!(__traits(compiles, new Object))(100);
>     }
>     auto allocateString(bool allowGC)(size_t size)
>     {
>         static if (allowGC)
>         {
>             return new char[size];
>         }
>         else
>         {
>             import core.stdc.stdlib : malloc;
>             return (cast(char*)malloc(size))[0 .. size];
>         }
>     }
> }
> else version (UseCompilesAtCallSite)
> {
>     void nogcExample() @nogc
>     {
>         allocateString(100);
>     }
>     void gcExample()
>     {
>         allocateString(100);
>     }
>     auto allocateString(bool allowGC = __traits(compilesAtCallSite, new Object))(size_t size)
>     {
>         static if (allowGC)
>         {
>             return new char[size];
>         }
>         else
>         {
>             import core.stdc.stdlib : malloc;
>             return (cast(char*)malloc(size))[0 .. size];
>         }
>     }
> }
>
> Then again maybe if the nogcExample/gcExample functions were also templates, then we wouldn't be able to infer whether or not gc was available...not sure on that one.

Another thought. If you wanted to propagate this "allowGC" information, you would probably have to explicitly pass it down each template.  For example, if the "nogcExample" and "gcExample" functions were also templates that could optionally use GC, then they would have to explicitly pass that information to the "allocateString template".  If that's the case then this doesn't scale.

It looks like if you implement this solution fully, the template parameters become analogous to function attributes.  So maybe for specific attributes, allowing them to be overloaded might the right way to go.  Of course, this brings us back to Andrei's original question which is whether or not allowing attributes to be overloaded would be worth the trouble to support.

May 28, 2019
On 5/28/2019 12:49 PM, Jonathan Marler wrote:
> You're right it's undecidable inside the function, but I think it's decidable if you check it at the call site.

Yet the call site may also be doing attribute inference.

Think of a graph with nodes in it, all interconnected with arbitrary edges, including cycles, and you have to find a set of attributes that satisfy each of the edges in it, when adding any attribute changes the topology of the graph.

Even if there is an algorithm which can solve this, and I don't have a PhD in graph theory and have no idea if there is one or not:

1. there may be N solutions - which one is picked?
2. how do you explain to the user why?
3. how do you explain to the user when N is zero?
4. the combinatorics of this may mean it takes essentially infinite time
5. I have a hard enough time implementing/debugging the current bottom-up method
6. I've spent years dealing with problems with forward references, when there are cycles in the reference graph and incomplete types. Your proposal makes that infinitely worse.

Like I said, it looks workable for trivial boundary cases, but that isn't how things work when people start using such a feature.

So, no. Hell no :-)
May 28, 2019
On Tuesday, 28 May 2019 at 20:26:50 UTC, Walter Bright wrote:
> On 5/28/2019 12:49 PM, Jonathan Marler wrote:
>> You're right it's undecidable inside the function, but I think it's decidable if you check it at the call site.
>
> Yet the call site may also be doing attribute inference.
>
> Think of a graph with nodes in it, all interconnected with arbitrary edges, including cycles, and you have to find a set of attributes that satisfy each of the edges in it, when adding any attribute changes the topology of the graph.
>
> Even if there is an algorithm which can solve this, and I don't have a PhD in graph theory and have no idea if there is one or not:
>
> 1. there may be N solutions - which one is picked?
> 2. how do you explain to the user why?
> 3. how do you explain to the user when N is zero?
> 4. the combinatorics of this may mean it takes essentially infinite time
> 5. I have a hard enough time implementing/debugging the current bottom-up method
> 6. I've spent years dealing with problems with forward references, when there are cycles in the reference graph and incomplete types. Your proposal makes that infinitely worse.
>
> Like I said, it looks workable for trivial boundary cases, but that isn't how things work when people start using such a feature.
>
> So, no. Hell no :-)

Unfortunately my mathematical emphasis was on number theory and combinatorics...and it was only a bachelors :)

But after sending my initial response I realized that if my example functions were also templates, then they would also have to use the allowGC = __traits(callSiteCompiles, new Object).  In fact, in order to propogate the information, every template function that could eventually call one of these allowGC templates would also need to add the allowGC parameter to their template parameter list.  Completely unscalable.

That being said, even though the solution is unscalable and verbose, it does actually work.  The compiler today is able to infer these attributes as demonstrated by the first version in my example code I showed earlier.  And it will also work no matter how deep the rabit hole goes...turtles all the way down.

It works because it determines the GC requirement top-down starting from the first non-template.  Since non-templates do not infer the @nogc attribute, the compiler already knows the answer to this inside each function that isn't a template.  So we just need a way to propagate that information to each template instantiated inside that function which is what the template parameter allowGC did.  We know this solution doesn't scale when using template parameters, but it would work if the compiler kept track of this internally.  You'd just need to add a boolean variable to each template instance that indicates whether the caller allowed the GC or not, that is passed in when the template is instantiated.

Anyway, I'm not saying we should do it, or even that we should do it this way, just saying it could be done.  The math here is actually pretty simple, no need for a PhD this time.

« First   ‹ Prev
1 2 3 4 5