Rant after trying Rust a bit
Jonathan M Davis via Digitalmars-d
digitalmars-d at puremagic.com
Sat Jul 25 23:54:15 PDT 2015
On Sunday, 26 July 2015 at 06:12:55 UTC, jmh530 wrote:
> I appreciate the thorough response. I think I agree with your
> point about error messages. Nevertheless, with respect to your
> point about a best effort to putting constraints at the
> top-level, there might be scope for making this easier for
> people. For instance, if there were a way to include the
> constraints from one template in another template. From your
> example, maybe something like
>
> auto foo(T)(T t)
> if(template_constraints!bar && template_constraints!baz)
> {
> ...
> auto b = bar(t);
> ...
> auto c = baz(t);
> ...
> }
>
> Ideally the template_constraints!bar would expand so that in an
> error message the user sees what the actual constraints are
> instead of the more nebulous template_constraints!bar. At least
> something like this would avoid your point with respect to
> __traits(compiles, x).
That's certainly an interesting idea, though if we're going that
route, it might be better to simply have the compiler do that
automatically for you, since it can see what functions are being
called and what they're constraints are. Still, part of the
problem is that the constraints for bar or baz may not be
directly related to the argument to foo but rather to a result of
operating on it. So, bar and baz's constraints can't necessarily
be moved up into foo's constraint like that in a meaningful way.
It would require the code that generates the arguments to bar and
baz as well in order to make that clear. For instance, it might
be that T needs to be an input range for the code that's directly
in foo. However, if you do something like
auto u = t.f1().f2().f3();
auto b = bar(u);
u then needs to be a forward range to work with bar. In order for
that to happen, t likely needs to have been a forward range, but
if you tried to move bar's template constraint into foo's
template constraint, it would be trying to require that u was a
forward range - because that's what bar needs - whereas that's r
really not what needs to be in foo's template constraint. What it
needs is to require that t be a forward range instead of just an
input range. The connection between the arguments the user is
passing to the function that they're calling and the arguments to
templated functions that are called within that function aren't
necessarily straightforward. So, moving those sub-constraints up
into the top-level constraint in a way that's clear to the caller
either requires that the writer of that function do it (because
they are able to understand how the function's argument relates
to the requirements of the functions being called within that
function and thus come up with the full requirements of for the
function's argument), or it requires that enough context be given
to the caller for them to be able to understand how what they're
passing in is related to the call inside the template that's
failing to compile because that function's argument doesn't pass
its constraint.
Without having the program who's writing this function
translating the sub-constraints into what the requirements then
are on the function's argument and need to go in the top-level
constraint, I don't see how we can avoid providing at least
_some_ of the source in error messages in order to make the
context of the failure clear. Simply shoving all of that into the
template constraint - even automatically - is just going to get
ugly outside of basic cases. And really, even then, what you're
trying to do is to take the context of the failure and put it in
the template constraint rather than just show that context along
with the failure. The more I think about it, the harder it seems
to be able to provide enough information to the caller without
pretty much just showing them the source code. The compiler
should be able to reduce how much of the source code would have
to be shown and thus avoid forcing the user to go look at the
templates full source, but in anything but the most basic cases,
that source code quickly becomes required to understand what's
going on.
If we're truly dealing with cases where the function's argument
is simply passed on to another function call, then the sort of
thing that you're suggesting is pretty straightforward and would
likely work well. But there are going to be a lot of cases where
the constraint failure isn't on the original function argument
but on the result of passing it through other functions or on
something that was obtained by calling one of that argument's
member functions. And as soon as that's what's going on,
attempting to push the sub-constraints into the top-level
constraint either by automatically inferring them or by having
something like template_constraints!baz is not going to work
well, if at all.
I don't know. I think that we can come up with solutions that fix
many of the simple cases, but I also think that a lot of the
simple cases are where it's easiest to maintain the top-level
template constraints with all of the sub-constraints translated
into top-level constraints. It's the complicated cases (which are
going to be common) where things get ugly. And I really don't see
a good solution.
Improved error messages obviously will help, but if the code is
complicated enough, it eventually reaches the point that the
caller is going to need to look at the full source code to see
what's going on and figure out what they're screwing up, so I
don't know how far we can go with the error messages. And it's
also those cases where it's probably going to be hardest to
maintain the constraints. It's a tough problem.
- Jonathan M Davis
More information about the Digitalmars-d
mailing list