www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - [DIP] Resolution of Alias Template Parameters in Function Templates

reply Stefanos Baziotis <sdi1600105 di.uoa.gr> writes:
Here is a DIP that I've been working the last couple of days:
https://github.com/baziotis/DIPs/blob/master/DIPs/DIP1021.md

It is a proposal about how to handle alias templates when they 
appear
as parameters in function templates.
It was initially proposed by Ilya here: 
https://forum.dlang.org/post/kvcrsoqozrflxibgxtlo forum.dlang.org

as a GSoC project and then again by Seb as part of the DataFrame 
project:
https://forum.dlang.org/post/jyzgzxqgaggltgifwnxx forum.dlang.org

There are also a couple of Bugzilla reports, 2 of those 
referenced in the DIP.

I tried to make it as much of a scientific paper as possible, but 
note that
I don't have experience either in writing papers or DIPs.

The DIP references a PR with a 3-day prototype implementation. 
That was an initial
implementation that I was working before identifying all the 
corner cases of the feature.
And the corner cases make it such that probably a lot of time has 
to be spent
for this feature to be fully supported. I believe however that it 
might come in handy
as it handles a lot of the Slice cases, which are the primal 
motivation behind this.
This is not my main GSoC project so I won't have to work on that 
on the summer, but if anyone wants to work on it, please keep me 
posted.
The DIP also references an article that explains the problem in a 
more informal fashion style.
I'm planning to change this article to be more 
implementation-specific where I will analyze
the most important problems of implementing this fully.

Best,
Stefanos
May 18
next sibling parent reply jmh530 <john.michael.hall gmail.com> writes:
On Saturday, 18 May 2019 at 12:32:13 UTC, Stefanos Baziotis wrote:
 Here is a DIP that I've been working the last couple of days:
 https://github.com/baziotis/DIPs/blob/master/DIPs/DIP1021.md

 It is a proposal about how to handle alias templates when they 
 appear
 as parameters in function templates.
 It was initially proposed by Ilya here: 
 https://forum.dlang.org/post/kvcrsoqozrflxibgxtlo forum.dlang.org

 as a GSoC project and then again by Seb as part of the 
 DataFrame project:
 https://forum.dlang.org/post/jyzgzxqgaggltgifwnxx forum.dlang.org

 There are also a couple of Bugzilla reports, 2 of those 
 referenced in the DIP.

 I tried to make it as much of a scientific paper as possible, 
 but note that
 I don't have experience either in writing papers or DIPs.

 The DIP references a PR with a 3-day prototype implementation. 
 That was an initial
 implementation that I was working before identifying all the 
 corner cases of the feature.
 And the corner cases make it such that probably a lot of time 
 has to be spent
 for this feature to be fully supported. I believe however that 
 it might come in handy
 as it handles a lot of the Slice cases, which are the primal 
 motivation behind this.
 This is not my main GSoC project so I won't have to work on 
 that on the summer, but if anyone wants to work on it, please 
 keep me posted.
 The DIP also references an article that explains the problem in 
 a more informal fashion style.
 I'm planning to change this article to be more 
 implementation-specific where I will analyze
 the most important problems of implementing this fully.

 Best,
 Stefanos
Thanks for working on this. You might remove the line "People seem to pay money for this feature to be implemented." While people (like Ilya and me) are willing to pay for this, it's not really relevant to the technical issues. Also, the forum has been discussing recently using static ifs within a function instead of function overloads. I think the same argument applies here. For instance, if switching to the commented code, then the writeln("here") will get skipped for writeln("there"). Correspondingly, I think it would be nice for the DIP to also discuss resolving is expressions as well. struct Slice(T) { } struct StairsIterator(T, string direction) { } alias PackedUpperTriangularMatrix(T) = Slice!(StairsIterator!(T*, "-")); auto foo(T)(T m) { import std.stdio : writeln; static if (is(T : Slice!(StairsIterator!(U*, "-")), U)) { //static if (is(T : PackedUpperTriangularMatrix!U, U)) { writeln("here"); } else static if (is(T : Slice!U, U)) { writeln("there"); } else { static assert(0, "should not be here"); } } void main() { Slice!int x; PackedUpperTriangularMatrix!int y; foo(x); foo(y); import std.stdio : writeln; writeln(is(typeof(y) : PackedUpperTriangularMatrix!U, U)); //prints false writeln(is(typeof(y) : Slice!(StairsIterator!(U*, "-")), U)); //prints true }
May 18
parent reply Stefanos Baziotis <sdi1600105 di.uoa.gr> writes:
On Saturday, 18 May 2019 at 16:05:10 UTC, jmh530 wrote:
 Thanks for working on this.
Your welcome.
 You might remove the line
 "People seem to pay money for this feature to be implemented."
 While people (like Ilya and me) are willing to pay for this, 
 it's not really relevant to the technical issues.
Good point, this is probably the least edited part of the code. It probably came from early draft versions. Removed.
 Also, the forum has been discussing recently using static ifs 
 within a function instead of function overloads. I think the 
 same argument applies here. For instance, if switching to the 
 commented code, then the writeln("here") will get skipped for 
 writeln("there"). Correspondingly, I think it would be nice for 
 the DIP to also discuss resolving is expressions as well.
Thanks for the information, I was not aware. But, it's not really clear what the behaviour should be. Look point 2. here [1]. What I understand is that the general idea is to make aliases more identical to what they alias rather than distinguishing them. This DIP is a step towards that. That seems to be the direction of the compiler source code as well. On the other hand, the behaviour that you would expect in the examples distinguishes them. In my opinion, I think that there should be a clear direction towards the one or the other. But of course this is not necessarily what you, the community and / or the language maintainers want. In that case, because it is a matter of opinion, it is not (fortunately) up to me to decide. We just need to listen to what most people want / care about along with some rationale backing it up. :) That is, in what cases they are different and in what they're not. This could be as simple as "only in `is` case they are different" or way more complicated. [1] https://dlang.org/spec/declaration.html#alias
May 18
parent reply jmh530 <john.michael.hall gmail.com> writes:
On Saturday, 18 May 2019 at 16:52:45 UTC, Stefanos Baziotis wrote:
 [snip]

 Thanks for the information, I was not aware. But, it's not 
 really clear
 what the behaviour should be. Look point 2. here [1]. What I 
 understand
 is that the general idea is to make aliases more identical to 
 what they alias
 rather than distinguishing them.

 This DIP is a step towards that.

 That seems to be the direction of the compiler source code as 
 well.

 On the other hand, the behaviour that you would expect in the 
 examples distinguishes them.

 In my opinion, I think that there should be a clear direction 
 towards the one or the other.
 But of course this is not necessarily what you, the community 
 and / or the language
 maintainers want. In that case, because it is a matter of 
 opinion, it is not (fortunately) up to me to decide. We just 
 need to listen to what most people want / care about
 along with some rationale backing it up. :)

 That is, in what cases they are different and in what they're 
 not.
 This could be as simple as "only in `is` case they are 
 different" or way
 more complicated.


 [1] https://dlang.org/spec/declaration.html#alias
Hmm, the link to the DIP isn't working for me now. My rationale would be that, from a generic code perspective, it is very helpful if alias templates behave like aliases (including both is expressions and use in functions). There may be good reasons why that shouldn't or can't be the case, but I think the goal should be on allowing for the most generic code possible. It may be useful to include in the DIP the cases where alias templates can and cannot be used like aliases...
May 20
next sibling parent reply jmh530 <john.michael.hall gmail.com> writes:
On Monday, 20 May 2019 at 14:45:59 UTC, jmh530 wrote:
 [snip

 Hmm, the link to the DIP isn't working for me now.
Ah, found it again https://github.com/baziotis/DIPs/blob/master/Drafts/1NNN-SB.md I don't see that "Changing the parameter type might not be enough" caveat as being that big of an issue. I see the problem as coming from the alias alias TemplateAlias(S, V) = TemplateType!(S); There is no reason for V to be there. You could just as easily write alias TemplateAlias(S, V = null) = TemplateType!(S); However, the user may not be able to change TemplateAlias, in which case something like alias TemplateAlias(S) = TemplateAlias!(S, typeof(null)); might be an acceptable solution I think.
May 20
parent Stefanos Baziotis <sdi1600105 di.uoa.gr> writes:
On Monday, 20 May 2019 at 17:35:23 UTC, jmh530 wrote:
 I don't see that "Changing the parameter type might not be 
 enough" caveat as being that big of an issue. I see the problem 
 as coming from the alias
 alias TemplateAlias(S, V) = TemplateType!(S);
I agree with you, but there might be more to it than it meets the eye because of course, it is accepted by the language currently. Other than that, to end up that this is a problem of aliases is irrelevant to the DIP. It doesn't have to do with template functions or template aliases as parameters to template functions.
 There is no reason for V to be there. You could just as easily 
 write
 alias TemplateAlias(S, V = null) = TemplateType!(S);
This still causes the drop of a template function template parameter.
 However, the user may not be able to change TemplateAlias, in 
 which case something like
 alias TemplateAlias(S) = TemplateAlias!(S, typeof(null));
 might be an acceptable solution I think.
Yes, but that is how the user decides to solve it. Again, concluding about what aliases are acceptable and what not is a separate topic, which is of course important.
May 20
prev sibling parent reply Stefanos Baziotis <sdi1600105 di.uoa.gr> writes:
On Monday, 20 May 2019 at 14:45:59 UTC, jmh530 wrote:
 Hmm, the link to the DIP isn't working for me now.
Yes, I updated it yesterday, please use this link: https://github.com/baziotis/DIPs/blob/master/Drafts/1NNN-SB.md
 My rationale would be that, from a generic code perspective, it 
 is very helpful if alias templates behave like aliases 
 (including both is expressions and use in functions). There may 
 be good reasons why that shouldn't or can't be the case, but I 
 think the goal should be on allowing for the most generic code 
 possible.
I agree, but with the current proposal, they do behave like aliases in that, there is no distinction between the true type and the alias. Here is an example similar to yours above: alias Num = int; auto foo(T)(T m) { import std.stdio : writeln; static if (is(T : int)) { writeln("here"); } else static if (is(T : Num)) { writeln("there"); } else { static assert(0, "should not be here"); } } void main() { Num x; int y; foo(x); foo(y); } Both of them go to "here". The `Num` one does not go to "there". That is, it can't be distinguished whether we have an int because of an alias or because we actually had an int. And that I understand that it is one of the important points of aliases. Of course, I may have missed / misunderstood something badly in all of that, but I understand that distinguishing them (e.g. in your example above) makes template aliases different than normal aliases.
May 20
parent reply jmh530 <john.michael.hall gmail.com> writes:
On Monday, 20 May 2019 at 17:36:35 UTC, Stefanos Baziotis wrote:
 [snip]

 Both of them go to "here". The `Num` one does not go to 
 "there". That is, it can't
 be distinguished whether we have an int because of an alias or 
 because we
 actually had an int. And that I understand that it is one of 
 the important points
 of aliases.

 Of course, I may have missed / misunderstood something badly in 
 all of that,
 but I understand that distinguishing them (e.g. in your example 
 above)
 makes template aliases different than normal aliases.
Yes, that's what you would expect to happen with a normal alias. If you change the alias to alias Num(T) = T; and the second is statement to is(T : Num!U, U) then it becomes equivalent to what I had done above. This fails for the same reason as discussed above.
May 20
parent reply Stefanos Baziotis <sdi1600105 di.uoa.gr> writes:
On Monday, 20 May 2019 at 18:12:08 UTC, jmh530 wrote:

 If you change the alias to
 alias Num(T) = T;
 and the second is statement to
 is(T : Num!U, U)
 then it becomes equivalent to what I had done above. This fails 
 for the same reason as discussed above.
But with the above reasoning we want this to fail (that is, evaluate is(T == int) as true and go there) because aliases and template aliases are indistinguishable from the type they alias.
May 20
parent reply jmh530 <john.michael.hall gmail.com> writes:
On Monday, 20 May 2019 at 18:20:06 UTC, Stefanos Baziotis wrote:
 [snip]

 But with the above reasoning we want this to fail (that is, 
 evaluate is(T == int) as true and go there) because aliases and 
 template aliases are indistinguishable from
 the type they alias.
Sorry, it would be true for Num!int, but false for Num!float or something else.
May 20
parent reply jmh530 <john.michael.hall gmail.com> writes:
On Monday, 20 May 2019 at 19:05:06 UTC, jmh530 wrote:
 On Monday, 20 May 2019 at 18:20:06 UTC, Stefanos Baziotis wrote:
 [snip]

 But with the above reasoning we want this to fail (that is, 
 evaluate is(T == int) as true and go there) because aliases 
 and template aliases are indistinguishable from
 the type they alias.
Sorry, it would be true for Num!int, but false for Num!float or something else.
It makes more sense if I said it should evaluate is(T==int) in the first static if for Num!int and then go to the second static if and evaluate is(T==float) for Num!float. That's just because of the way you have the static ifs set up.
May 20
parent reply Stefanos Baziotis <sdi1600105 di.uoa.gr> writes:
On Monday, 20 May 2019 at 19:07:49 UTC, jmh530 wrote:
 On Monday, 20 May 2019 at 19:05:06 UTC, jmh530 wrote:
 On Monday, 20 May 2019 at 18:20:06 UTC, Stefanos Baziotis 
 wrote:
 [snip]

 But with the above reasoning we want this to fail (that is, 
 evaluate is(T == int) as true and go there) because aliases 
 and template aliases are indistinguishable from
 the type they alias.
Sorry, it would be true for Num!int, but false for Num!float or something else.
It makes more sense if I said it should evaluate is(T==int) in the first static if for Num!int and then go to the second static if and evaluate is(T==float) for Num!float. That's just because of the way you have the static ifs set up.
If the second static if is `if(is(T == float))` and we passed float or Num!float, yes it should go there. If it is as what we said earlier, `is(T : Num!U, U)` it should not evaluate it to true with the reasoning I said above. That is, it doesn't distinguish an int from a Num!int or a float from a Num!float. It goes back to the assumption that aliases and template aliases are indistinguishable from the type they alias. Again, that is what I _assume_ that is the general direction. The DIP is a step towards that (basically, the root of the problem that the DIP solves is that the compiler sees the alias and the actual type as 2 different types). Also, that is what _I_ would think as the better alternative of the 2. That is not to say that it is the correct / best one. Certainly, it's what the compiler source seems to move towards. During the semantic analysis, the type of a Num!int variable, becomes an int. I surely don't have a lot of experience with the compiler internals, but as far as I know, there's no clear / easy way to know whether it came from an alias or an int.
May 20
parent reply jmh530 <john.michael.hall gmail.com> writes:
On Monday, 20 May 2019 at 19:25:37 UTC, Stefanos Baziotis wrote:
 [snip]
Sorry, I was getting confused. I was thinking the Num was from a struct, rather than from an alias. Yes, you're right.
May 20
parent Stefanos Baziotis <sdi1600105 di.uoa.gr> writes:
On Monday, 20 May 2019 at 19:44:57 UTC, jmh530 wrote:
 On Monday, 20 May 2019 at 19:25:37 UTC, Stefanos Baziotis wrote:
 [snip]
Sorry, I was getting confused. I was thinking the Num was from a struct, rather than from an alias. Yes, you're right.
It's ok, thank you for the feedback!
May 20
prev sibling parent reply Nick Treleaven <nick geany.org> writes:
On Saturday, 18 May 2019 at 12:32:13 UTC, Stefanos Baziotis wrote:
| https://github.com/baziotis/DIPs/blob/master/DIPs/DIP1021.md

Assume you mean: 
https://github.com/baziotis/DIPs/blob/master/Drafts/1NNN-SB.md
May 20
parent Stefanos Baziotis <sdi1600105 di.uoa.gr> writes:
On Monday, 20 May 2019 at 14:27:48 UTC, Nick Treleaven wrote:
 On Saturday, 18 May 2019 at 12:32:13 UTC, Stefanos Baziotis 
 wrote:
 | https://github.com/baziotis/DIPs/blob/master/DIPs/DIP1021.md

 Assume you mean: 
 https://github.com/baziotis/DIPs/blob/master/Drafts/1NNN-SB.md
Yes, thank you, I updated it just yesterday and forgot it.
May 20