Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[New DIP] Multiple Template constraints #131

Open
wants to merge 41 commits into
base: master
Choose a base branch
from
Open
Changes from 37 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
4e20e93
Initial draft of Multiple template constraints
thewilsonator Aug 5, 2018
ea94792
Fix code blocks
thewilsonator Aug 5, 2018
ae1ab34
Two more
thewilsonator Aug 5, 2018
daf7279
Link to implementation
thewilsonator Aug 6, 2018
3a5b70c
Add countUntil example
thewilsonator Aug 7, 2018
9e9fc93
Add note that this applies for functions, methods, classes and struct…
thewilsonator Aug 7, 2018
e6573bb
wording
thewilsonator Aug 7, 2018
8568562
Note not implemented yet
thewilsonator Aug 7, 2018
61d147d
Fix error message
thewilsonator Aug 7, 2018
6a15ec6
Again
thewilsonator Aug 7, 2018
e388f2a
add missing output
thewilsonator Aug 7, 2018
7b8d07c
add missing `
thewilsonator Aug 8, 2018
ee6016d
Fix messages
thewilsonator Aug 8, 2018
e0b4d0b
remove less descriptive message
thewilsonator Aug 8, 2018
0e6130c
Correct expressions for if with message
thewilsonator Aug 8, 2018
44a1b16
Add section about static foreach
thewilsonator Aug 8, 2018
3f2eca3
Remove unneeded text in breaking changes
thewilsonator Aug 8, 2018
40d1fa5
make static foreach match the grammar
thewilsonator Aug 8, 2018
298d779
fix
thewilsonator Aug 8, 2018
a9b623b
fix
thewilsonator Aug 8, 2018
997e31e
indent
thewilsonator Aug 8, 2018
5d8991e
add countuntil's third overload
thewilsonator Aug 9, 2018
20b9abc
Set status to draft
thewilsonator Aug 9, 2018
d084cd6
Make `if` template constraints like `in` contracts.
thewilsonator Aug 17, 2018
0644d18
missing `
thewilsonator Aug 26, 2018
a2a3936
Remove reference template text
thewilsonator Aug 26, 2018
281858e
Fix sentence flow.
thewilsonator Aug 26, 2018
ba5323e
Add block statement form to description
thewilsonator Aug 26, 2018
b539519
add interface and union to the description.
thewilsonator Aug 26, 2018
b1d9398
ditto template mixin
thewilsonator Aug 26, 2018
39ade6b
Don't make it sound like only one block constraint is allowed
thewilsonator Aug 26, 2018
6d6cf4d
Reword abstract
thewilsonator Aug 26, 2018
d29e19e
Add references
thewilsonator Aug 26, 2018
dfc309e
Typo
thewilsonator Aug 26, 2018
ada290a
Update DIP1xxx.md
thewilsonator Aug 26, 2018
c59a636
Update DIP1xxx.md
thewilsonator Aug 26, 2018
0d607bf
Update DIP1xxx.md
thewilsonator Aug 26, 2018
31f6b87
Update DIP1xxx.md
thewilsonator Jan 14, 2019
3a9c1f4
Fix moar typos!
thewilsonator Jan 14, 2019
5b25117
Restore balance to the ~~force~~ parentheses.
thewilsonator Jan 16, 2019
155efcf
Update DIPs/DIP1xxx.md
mleise Jan 16, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
249 changes: 249 additions & 0 deletions DIPs/DIP1xxx.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
# Expression and Block Statement Template Constraints

| Field | Value |
|-----------------|-----------------------------------------------------------------|
| DIP: | (number/id -- assigned by DIP Manager) |
| Review Count: | 0 |
| Author: | Nicholas Wilson |
| Implementation: | https://github.com/thewilsonator/dmd/tree/template-constraint-dip |
| Status: | Draft |

## Abstract

Allow multiple `if` template constraints, for the expression form allow an optional message to be printed in the
event that overload resolution fails (similar to `static assert`), as well as a block statement
form of template constraint that allows the use of `static foreach`.
That is to say, template constraint `if` becomes the static form of contract precondition `in`.

The template is considered a valid overload iff each of the constraints is satified.

Expression form:
```D
template all(alias pred)
{
bool all(Range)(Range range)
if (isInputRange!Range)
if (is(typeof(unaryFun!pred(range.front))),
"`" ~ pred.stringof[1..$-1] ~ "` isn't a unary predicate function for range.front"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it's not a "unary predicate", but the user needs to know why.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You do now. Before you would have first had to determine which sub constraint had failed.

To provide more information the compiler needs to determine if unaryFun!pred failed instantiation, and then if unaryFun!pred(range.front) is type correct.

I'm not saying it can't be done, in fact it is enabled by this DIP but that relies on errors in speculative code which has a very poor signal to noise ratio, or a set of heuristics as to what template constraints tend to look like.

thewilsonator marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This variant isn't equivalent to the two below, right? Presenting three forms of the same equivalent contracts would help comparing them.

{
}
}
```

Block statement form:
```D
ptrdiff_t countUntil(alias pred = "a == b", R, Rs...)(R haystack, Rs needles)
if
{
static assert(isForwardRange!R);
static assert(Rs.length > 0, "need a needle to countUntil with");
thewilsonator marked this conversation as resolved.
Show resolved Hide resolved
static foreach (alias N; Rs)
static assert(isForwardRange!(N) == isInputRange!(N), "needles that are ranges must be forward ranges");
static foreach (n; needles)
static assert(is(typeof(startsWith!pred(haystack, n))),
"predicate `" ~ pred.stringof "` must be valid for `startsWith!pred(haystack, "~n.stringof~"`));
}
```

Mixed:

```D
ptrdiff_t countUntil(alias pred = "a == b", R, Rs...)(R haystack, Rs needles)
if(isForwardRange!R)
if(Rs.length > 0, "need a needle to countUntil with")
if
{
static foreach (alias N; Rs)
static assert(isForwardRange!(N) == isInputRange!(N), "needles that are ranges must be forward ranges");
static foreach (n; needles)
static assert(is(typeof(startsWith!pred(haystack, n))),
"predicate `" ~ pred.stringof "` must be valid for `startsWith!pred(haystack, "~n.stringof~"`));
}
```

### Reference

https://github.com/dlang/phobos/pull/6607

https://issues.dlang.org/show_bug.cgi?id=13683

## Contents
* [Rationale](#rationale)
* [Description](#description)
* [Breaking Changes and Deprecations](#breaking-changes-and-deprecations)
* [Copyright & License](#copyright--license)
* [Reviews](#reviews)

## Rationale

It is well known that compilation error messages due to template contraint overload resolution
are particularly difficult to decipher. This is not helped by the number of overloads and very
precice (and thus complex) constraints placed on the overloads. When overload resolution fails
the compiler will print out all the in scope overloads and their constraints, without indication
of which constraints have failed.

While it is not possible in the general case to provide useful information as to what constraints
have failed and why, because a constraint may have an arbitrary combination of logic, the vast
majority of constraints are expressed in Conjunctive Normal Form (CNF). In this case it is definitely
possible to provide better daignostics as to which clauses have failed. However the current grammer
thewilsonator marked this conversation as resolved.
Show resolved Hide resolved
provides no way to translate particularly verbose constraints to a user not intimately familiar with
the constraint.

This DIP therefore proposes to formalise the use of CNF constraints by allowing multiple `if` constraints,
the expression form with an optional message (similar to what was done with contracts in DIP1009), as well as block statements that
allows the use of `static foreach` and to declare `alias`es and `enum`s to eliminate the need for recursive templates in template constrains
thewilsonator marked this conversation as resolved.
Show resolved Hide resolved
(similar to the `in` contract form prior to DIP1009).
This will put the compiler in a much better position to provide useful diagnostics, such as indicating which clauses are not satisfied
and allowing the template author to provide messages in the case of non-intuitive formulations of constraints
e.g. `if(isForwardRange!(R) == isInputRange!(R), "needles that are ranges must be forward ranges")`.

Using the particularly egregious example of the first overload of `std.algorithm.searching.countUntil`,
its current signature of

```D
ptrdiff_t countUntil(alias pred = "a == b", R, Rs...)(R haystack, Rs needles)
if (isForwardRange!R
&& Rs.length > 0
&& isForwardRange!(Rs[0]) == isInputRange!(Rs[0])
&& is(typeof(startsWith!pred(haystack, needles[0])))
&& (Rs.length == 1
|| is(typeof(countUntil!pred(haystack, needles[1 .. $])))))
```

would be be written using the block statement form to elimiate the recursive constraint as
thewilsonator marked this conversation as resolved.
Show resolved Hide resolved

```D
ptrdiff_t countUntil(alias pred = "a == b", R, Rs...)(R haystack, Rs needles)
if(isForwardRange!R)
thewilsonator marked this conversation as resolved.
Show resolved Hide resolved
if(Rs.length > 0, "need a needle to countUntil with") // example messge, probably not needed for something this simple
if
{
static foreach (n; needles)
{
static assert(isForwardRange!(typeof(n)) == isInputRange!(typeof(n)),
"`"~n.stringof ~"`: needles that are ranges must be forward ranges");
static assert(is(typeof(startsWith!pred(haystack, n))),
"predicate `" ~ pred.stringof "` must be valid for"~
"`startsWith!pred(haystack, "~n.stringof~"`));
}
}
```
the first two constraints do not require the use of the block statement form to use a static foreach so
they can be done in the expression style.

This could print on error using `countUntil("foo", inputRangeOfInt)`
```
example.d(42): Error: template `std.algorithm.searching.countUntil` cannot deduce function from argument types !()(string,NotARange), candidates are:
/path/to/std/algorithm/searching.d(747): std.algorithm.searching.countUntil(alias pred = "a == b", R, Rs...)(R haystack, Rs needles)
not satisfied: `inputRangeOfInt`: needles that are ranges must be forward ranges
not satisfied: predicate `a == b` must be valid for `startsWith!pred(haystack, inputRangeOfInt)`
/path/to/std/algorithm/searching.d(835): std.algorithm.searching.countUntil(alias pred = "a == b", R, N)(R haystack, N needle) if (isInputRange!R && is(typeof(binaryFun!pred(haystack.front, needle)) : bool))
/path/to/std/algorithm/searching.d(913): std.algorithm.searching.countUntil(alias pred, R)(R haystack) if (isInputRange!R && is(typeof(unaryFun!pred(haystack.front)) : bool))
```

## Description

Template constraints are changed to allow multiple `if` template constraints.
All constraints must be satisfied for the template to be viable.
Examples given are for `template`s but also apply to constraints on template mixins, functions, methods, classes,
interfaces, structs and unions.

### Expression Form

The expression form is:
```D
template foo(T)
if (constraint1!T)
if (constraint2!T)
if (constraint3!T) { ... }
```

An optional constraint message can be used to provide a more easily uderstood description of why a
thewilsonator marked this conversation as resolved.
Show resolved Hide resolved
constraint has not been met.

```D
template foo(T)
if (isForwardRange!T == isInputRange!T, T.stringof ~" must be a forward range if it is a range")
```
###Block Statement Form

The block statement form is:
```D
template foo(T)
if
{
...constraints...
}
```
where `...constraints...` may contain only `static assert`, `static foreach` and `static if` statements and `enum` and `alias` declarations.
The declarations are local to the scope of the constraint or `static foreach` or `static if` they are declared in.
Each `static assert` in the block statement in satisfied `static if` statements,
including those in unrolled `static foreach` statements, must pass for the constraint to be satisfied.

###Grammar changes
thewilsonator marked this conversation as resolved.
Show resolved Hide resolved

```diff
+Constraints:
+ Constraint
+ Constraint Constraints

Constraint:
- if ( Expression )
+ if ( AssertArguments )
+ if BlockStatement

FuncDeclaratorSuffix:
Parameters MemberFunctionAttributes[opt]
- TemplateParameters Parameters MemberFunctionAttributes[opt] Constraint[opt]
+ TemplateParameters Parameters MemberFunctionAttributes[opt] Constraints[opt]

TemplateDeclaration:
- template Identifier TemplateParameters Constraint[opt] { DeclDefs[opt] }
+ template Identifier TemplateParameters Constraints[opt] { DeclDefs[opt] }

ConstructorTemplate:
- this TemplateParameters Parameters MemberFunctionAttributes[opt] Constraint[opt] :
- this TemplateParameters Parameters MemberFunctionAttributes[opt] Constraint[opt] FunctionBody
+ this TemplateParameters Parameters MemberFunctionAttributes[opt] Constraints[opt] :
+ this TemplateParameters Parameters MemberFunctionAttributes[opt] Constraints[opt] FunctionBody

ClassTemplateDeclaration:
- class Identifier TemplateParameters Constraint[opt] BaseClassList[opt] AggregateBody
- class Identifier TemplateParameters BaseClassList[opt] Constraint[opt] AggregateBody
+ class Identifier TemplateParameters Constraints[opt] BaseClassList[opt] AggregateBody
+ class Identifier TemplateParameters BaseClassList[opt] Constraints[opt] AggregateBody

InterfaceTemplateDeclaration:
- interface Identifier TemplateParameters Constraint[opt] BaseInterfaceList[opt] AggregateBody
- interface Identifier TemplateParameters BaseInterfaceList Constraint AggregateBody
+ interface Identifier TemplateParameters Constraints[opt] BaseInterfaceList[opt] AggregateBody
+ interface Identifier TemplateParameters BaseInterfaceList Constraints AggregateBody

StructTemplateDeclaration:
- struct Identifier TemplateParameters Constraint[opt] AggregateBody
+ struct Identifier TemplateParameters Constraints[opt] AggregateBody

UnionTemplateDeclaration:
- union Identifier TemplateParameters Constraint[opt] AggregateBody
+ union Identifier TemplateParameters Constraints[opt] AggregateBody

TemplateMixinDeclaration:
- mixin template Identifier TemplateParameters Constraint[opt] { DeclDefs[opt] }
+ mixin template Identifier TemplateParameters Constraints[opt] { DeclDefs[opt] }
```


## Breaking Changes and Deprecations

N/A. The current template constraint syntax becomes a single expression constraint.

## Copyright & License

Copyright (c) 2018 by the D Language Foundation

Licensed under [Creative Commons Zero 1.0](https://creativecommons.org/publicdomain/zero/1.0/legalcode.txt)

## Reviews

The DIP Manager will supplement this section with a summary of each review stage
of the DIP process beyond the Draft Review.