Yes, constraints can have helpful error messages!
Marvin Hannott
i28muzw0n at relay.firefox.com
Sat Apr 9 01:03:32 UTC 2022
I was a bit surprised, reading the discussions about how
constraints aren't very helpful when it comes to figuring out
what is wrong. And I was even more surprised that of all things
`pragma` was called to the rescue. But isn't there a much better,
simpler way?
Now, I don't like being the idiot who thinks he found a gold vein
in no man's land, thinking that no one has ever considered my
approach, so please be critical. And though I got some experience
in D, I am by no means an expert.
But let's just have a look at the constraint `isFilter`, which
checks whether a function is a suitable filter (for FilterRange,
or whatever. Let's not overthink it).
```D
template isFilter(alias filter, bool asserts = false)
{
enum isFilter=
{
import std.traits : ReturnType, Parameters, isMutable,
isScalarType;
alias RT = ReturnType!(typeof(filter));
static if(!is(RT == bool))
{
static assert(!asserts,expect!(RT, bool, "Return"));
return false;
}
alias params = Parameters!filter;
static if(params.length != 1)
{
static assert(!asserts, expect!(cast(int)
params.length, 1,
"Number of arguments"));
return false;
}
alias param = params[0];
static if(isMutable!param && !isScalarType!param)
{
static assert!(!asserts, "Argument must be constant
or a scalar type");
return false;
}
return true;
}();
}
template expect(alias actual, alias expected,string descr)
{
enum expect=
descr~": Got '"~actual.stringof~"', but expected
'"~expected.stringof~"'";
}
template expect(Actual, Expected, string descr, bool convertable
= false)
{
enum expect =
{
static if(convertable)
{
enum equalType = is(Actual : Expected);
}
else
{
enum equalType = is(Actual == Expected);
}
return !equalType ?
descr~": Got '"~Actual.stringof~"', but expected
'"~Expected.stringof~"'" : "";
}();
}
```
I think this is a reasonably complex example that demonstrates my
point. (Let's not focus on whether these conditions are actually
reasonable or not.)
As you can see, there is just a simple template switch which
controls whether an `AssertionError` will be thrown at
compile-time or not. What that means is that `isFilter` can be
used as regular constraint in an `if()`-statement, but also as
compile-time interface (in this case most likely inside a
function body) that gives helpful error messages. And I would
daresay that this method is reasonably convenient, and could
certainly be made even more convenient with more helper functions
and/or mixins.
And best of all: it's completely backwards compatible (if
`asserts` defaults to `false`)!
So, why aren't we doing this? Is it really just because of
`__traits(compiles,...)`, which some people have suggested (and
it is kinda everywhere)? But even if so, there is no reason this
wouldn't work with `__traits(compiles,...)` as well. Just need
some good helper functions.
More information about the Digitalmars-d
mailing list