www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Request for a more powerful template specialization feature

reply data pulverizer <data.pulverizer gmail.com> writes:
Dear all,

Template specializations are a great feature in D. They allow the 
programmer to create template specializations but they can also 
be a powerful way of constraining templates by implementing only 
the specializations that you need. In contrast template 
constraints can quickly become very complex for the programmer to 
write and reason about.

Template specializations should be extended to allow multiple 
lists of types to be implemented together. For example this ...

template Construct(R: Union{double, int}, W: Union{string, char, 
dchar})
{
     auto Construct(R, W)(R r, W w)
     {
         ....
     }
}

The same definition would be allowed for all 6 combinations of 
{double, int} and {string, char, dchar} (and no more! Unless 
specified otherwise and/or more generally). This would remove the 
need to manually write these for all combinations or resort to 
constraints.

In addition for some use cases it is a nicer way of doing unions 
than using the current union keyword. Union{double, int} could be 
a compile-time construct indicating that the type can be either 
an actual double or int. It can replace the use of union in cases 
where you want a substitution of either double or int for this 
Union rather than a union type. In addition, variants return 
variants rather than "properly" typed data.

It would be good to get comments and suggestions before I write a 
DIP.

Thank you in advance.
Jul 14
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.com> writes:
On 7/14/17 2:19 PM, data pulverizer wrote:
 template Construct(R: Union{double, int}, W: Union{string, char, dchar})
template Construct(R, W) if ((is(R == double) || is(R == int)) && (is(W == string) || is(W == char) || is(W == dchar))
 It would be good to get comments and suggestions before I write a DIP.
An effective way of improving the state of affairs would be to create a PR that makes the constraint easier to read and write, e.g.: among!(R, double, int) && among!(W, string, char, dchar) In fact it's surprising it hasn't been proposed yet. Andrei
Jul 14
next sibling parent reply Jonathan M Davis via Digitalmars-d <digitalmars-d puremagic.com> writes:
On Friday, July 14, 2017 3:49:05 PM MDT Andrei Alexandrescu via Digitalmars-
d wrote:
 On 7/14/17 2:19 PM, data pulverizer wrote:
 template Construct(R: Union{double, int}, W: Union{string, char, dchar})
template Construct(R, W) if ((is(R == double) || is(R == int)) && (is(W == string) || is(W == char) || is(W == dchar))
Yeah, I tend to forget that template specialization even exists in D, and I _really_ hate the fact that : in template specializations doesn't mean what it means in is expressions (equality instead of implicit conversion). So, I wish that we didn't even have template specializations, but presumably, it's not worth the breakage to remove them. The one thing that they do have going for them over template constraints though is that if you overload with template constraints, you tend to have to duplicate the constraint with the reverse condition, which you don't have to do with template specializations. But smart use of static ifs inside of templated functions can mitigate that problem, and ultimately, I think that template specialization is a redundant feature.
 It would be good to get comments and suggestions before I write a DIP.
An effective way of improving the state of affairs would be to create a PR that makes the constraint easier to read and write, e.g.: among!(R, double, int) && among!(W, string, char, dchar) In fact it's surprising it hasn't been proposed yet.
On seeing this thread, that's the first thing I thought of as well, and I assumed that it already existed but didn't take the time to track it down. Off the top of my head, I'd guess that you'd use staticIndexOf an check for -1 to implement it, which should be pretty straight forward, albeit slightly more verbose. Thinking on it now, I think that that's what I've done in the past. - Jonathan M Davis
Jul 14
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 07/14/2017 04:29 PM, Jonathan M Davis via Digitalmars-d wrote:
 I'd guess that you'd use staticIndexOf an check for
 -1 to implement it
Given that constraints are user-facing, having a forwarding one-liner may be justifiable. -- Andrei
Jul 14
parent Jonathan M Davis via Digitalmars-d <digitalmars-d puremagic.com> writes:
On Friday, July 14, 2017 4:45:10 PM MDT Andrei Alexandrescu via Digitalmars-
d wrote:
 On 07/14/2017 04:29 PM, Jonathan M Davis via Digitalmars-d wrote:
 I'd guess that you'd use staticIndexOf an check for
 -1 to implement it
Given that constraints are user-facing, having a forwarding one-liner may be justifiable. -- Andrei
Possibly. It's hard to know sometimes when a one line wrapper is a big enough boost to usability to standardize. I could see this being such a case. staticIndexOf is a bit unwieldy, and I've rarely used it, but when I have, I think that I've always used it to check whether somehing is present rather than getting an actual index. Another tough example would be takeWhile/dropWhile. We already of until and find, which do the same things respectively but with the opposite predicate such that they're arguably too simple to be worth adding, but at the same time, if you have to negate your predicates often enough, having takeWhile and dropWhile would be a definite boon (also, for many folks, takeWhile and dropWhile are obvious in a way that until or find might not be - especially until). Timon was complaining bitterly the other day about the lack of takeWhile: https://issues.dlang.org/show_bug.cgi?id=4535 IIRC, I tried to add those several years ago, but you vetoed them on the grounds that simply negating the predicate was trvial, which is certainly true, albeit less user-friendly. I don't know what the correct answer is though. Sometimes, it's clear that adding a one-liner is a pointless wrapper, whereas in other cases, it seems like a definite usabiliy improvement. - Jonathan M Davis
Jul 14
prev sibling next sibling parent reply Enamex <enamex+d outlook.com> writes:
On Friday, 14 July 2017 at 19:49:05 UTC, Andrei Alexandrescu 
wrote:
 On 7/14/17 2:19 PM, data pulverizer wrote:
 template Construct(R: Union{double, int}, W: Union{string, 
 char, dchar})
template Construct(R, W) if ((is(R == double) || is(R == int)) && (is(W == string) || is(W == char) || is(W == dchar))
 It would be good to get comments and suggestions before I 
 write a DIP.
An effective way of improving the state of affairs would be to create a PR that makes the constraint easier to read and write, e.g.: among!(R, double, int) && among!(W, string, char, dchar) In fact it's surprising it hasn't been proposed yet. Andrei
But specializations are quite different from constraints, no? Constraints wouldn't help when the template name is overloaded and passes the constraint checks of several different template implementations; specializations narrow things down for overload resolution. Unless I'm misunderstanding the OP or everyone else in the thread somehow :/
Jul 15
parent data pulverizer <data.pulverizer gmail.com> writes:
On Saturday, 15 July 2017 at 10:34:13 UTC, Enamex wrote:
 But specializations are quite different from constraints, no? 
 Constraints wouldn't help when the template name is overloaded 
 and passes the constraint checks of several different template 
 implementations; specializations narrow things down for 
 overload resolution.

 Unless I'm misunderstanding the OP or everyone else in the 
 thread somehow :/
That's exactly right, you've probably put it better than I have! Specializations are quite different from constraints and I have found as you said that specializations are a great way to narrow things down for overload resolution. I use them to dispatch methods and they are the easiest way to specify a known set of kinds on a method set. When you narrow to your specialization set like that it will always work in the same way - no rouge types will mess things up.
Jul 15
prev sibling parent reply jmh530 <john.michael.hall gmail.com> writes:
On Friday, 14 July 2017 at 19:49:05 UTC, Andrei Alexandrescu 
wrote:
 An effective way of improving the state of affairs would be to 
 create a PR that makes the constraint easier to read and write, 
 e.g.:

 among!(R, double, int) && among!(W, string, char, dchar)

 In fact it's surprising it hasn't been proposed yet.

 Andrei
Forgive me, but what about anySatisfy https://dlang.org/library/std/meta/any_satisfy.html
Jul 17
parent jmh530 <john.michael.hall gmail.com> writes:
On Monday, 17 July 2017 at 12:29:41 UTC, jmh530 wrote:
 Forgive me, but what about anySatisfy
 https://dlang.org/library/std/meta/any_satisfy.html
Oh sorry, I see that's a little different.
Jul 17
prev sibling parent reply data pulverizer <data.pulverizer gmail.com> writes:
On Friday, 14 July 2017 at 18:19:03 UTC, data pulverizer wrote:
 Dear all,

 Template specializations are a great feature in D. They allow 
 the programmer to create template specializations but they can 
 also be a powerful way of constraining templates by 
 implementing only the specializations that you need. In 
 contrast template constraints can quickly become very complex 
 for the programmer to write and reason about.

 Template specializations should be extended to allow multiple 
 lists of types to be implemented together. For example this ...

 template Construct(R: Union{double, int}, W: Union{string, 
 char, dchar})
 {
     auto Construct(R, W)(R r, W w)
     {
         ....
     }
 }

 The same definition would be allowed for all 6 combinations of 
 {double, int} and {string, char, dchar} (and no more! Unless 
 specified otherwise and/or more generally). This would remove 
 the need to manually write these for all combinations or resort 
 to constraints.

 In addition for some use cases it is a nicer way of doing 
 unions than using the current union keyword. Union{double, int} 
 could be a compile-time construct indicating that the type can 
 be either an actual double or int. It can replace the use of 
 union in cases where you want a substitution of either double 
 or int for this Union rather than a union type. In addition, 
 variants return variants rather than "properly" typed data.

 It would be good to get comments and suggestions before I write 
 a DIP.

 Thank you in advance.
I am aware that this suggestion touches the language and the compiler - and may significant implications. I would like to know whether this could be done without too much effort and whether it would break anything else? If you are writing lots of overloaded templates, constraints can have unintended behaviour because you end up telling the compiler what not to do rather than what to do. The above Unions are clear and simple, easy to use and should result in cleaner more robust code.
Jul 14
parent reply data pulverizer <data.pulverizer gmail.com> writes:
On Friday, 14 July 2017 at 22:25:15 UTC, data pulverizer wrote:
 I am aware that this suggestion touches the language and the 
 compiler - and may significant implications. I would like to 
 know whether this could be done without too much effort and 
 whether it would break anything else?

 If you are writing lots of overloaded templates, constraints 
 can have unintended behaviour because you end up telling the 
 compiler what not to do rather than what to do. The above 
 Unions are clear and simple, easy to use and should result in 
 cleaner more robust code.
In addition with template specializations you get constraints for free. If I implement template overloads which are all specializations, the compiler gives me a very informative error if I step outside the pre-defined set of implementations. That's just brilliant - exactly what you want! I immediately know when I see that error what the issue is. Am I being naive? Why are constraints better?
Jul 14
parent reply Stefan Koch <uplink.coder googlemail.com> writes:
On Friday, 14 July 2017 at 22:49:18 UTC, data pulverizer wrote:
 On Friday, 14 July 2017 at 22:25:15 UTC, data pulverizer wrote:
 I am aware that this suggestion touches the language and the 
 compiler - and may significant implications. I would like to 
 know whether this could be done without too much effort and 
 whether it would break anything else?

 If you are writing lots of overloaded templates, constraints 
 can have unintended behaviour because you end up telling the 
 compiler what not to do rather than what to do. The above 
 Unions are clear and simple, easy to use and should result in 
 cleaner more robust code.
In addition with template specializations you get constraints for free. If I implement template overloads which are all specializations, the compiler gives me a very informative error if I step outside the pre-defined set of implementations. That's just brilliant - exactly what you want! I immediately know when I see that error what the issue is. Am I being naive? Why are constraints better?
One important characteristic about constraints, is that they are not bound to types. Also they can from conjunctions and disjunctions. Combined with ctfe they are very flexible and powerful. I do not know how you would do the same with specializations. Then again being unfamiliar with template-specializations I might be overlooking something.
Jul 14
parent data pulverizer <data.pulverizer gmail.com> writes:
On Friday, 14 July 2017 at 23:04:48 UTC, Stefan Koch wrote:
 One important characteristic about constraints, is that they 
 are not bound to types.
 Also they can from conjunctions and disjunctions.
 Combined with ctfe they are very flexible and powerful.

 I do not know how you would do the same with specializations.
 Then again being unfamiliar with template-specializations I 
 might be overlooking something.
True ... it doesn't have to be specializations or constraints, as you have said constraints are important for conjunctions and disjunctions. I should not have said "why are constraints better than specializations". However when you are dealing with a know finite set of know types or constructs - which I do frequently, it is useful to be able to be able to specify the exact behaviour for individuals and different groups clearly and succinctly. For instance when writing mathematical algorithms that should apply differently to different constructs, its much easier to say "do this for that specific construct" and "do something else for some group" and so on without having to repeat negating cases. A practical case might be dealing with two library "kinds" each having around 8 possible type specifications where some associated methods are related. Sometimes methods will be the same for combinations of types both within each kind and across both kind combinations. Specializations that allow multiple values are really good for these cases. I currently use string mixins to code-gen my template specializations - its a good-enough but visually inelegant solution.
Jul 14