July 26, 2015
On Sunday, 26 July 2015 at 00:31:48 UTC, Jonathan M Davis wrote:
> auto foo(T)(T t)
>     if(cond1!T && cond2!T && cond3!T && cond4!T && cond5!T && cond6!T && cond7!T)
> {
>     ...
>     auto b = bar(t);
>     ...
>     auto c = baz(t);
>     ...
> }
>
> auto bar(T)(T t)
>     if(cond2!T && cond3!T)
> {
>     ...
>     auto l = lark(t);
>     ...
> }
>
> auto baz(T)(T t)
>     if(cond1!T && cond4!T)
> {
>     ...
>     auto s = stork(t);
>     ...
> }
>
>
> auto lark(T)(T t)
>     if(cond5!T && cond6!T)
> {
>     ...
> }
>
> auto stork(T)(T)
>     if(cond2!T && cond3!T && cond7!T)
> {
>     auto w = wolf(t);
> }
>
> auto wolf(T)(T)
>     if(cond7!T)
> {
>     ...
> }
>

Regardless of this debate, it would be great if template constraints could be inferred. It seems rather trivial.

Although I understand that a lot of times the compiler doesn't have the function's body at hand.
July 26, 2015
On Sunday, 26 July 2015 at 01:56:29 UTC, Tofu Ninja wrote:
> Key point is opt-in.

Opt-in doesn't really fix the problem. It just allows you to choose whether you're going to break more code by requiring that all template constraints be updated because a function being called inside somewhere had its constraint updated. So, you're choosing whether to opt-in to that problem or not, but if you opt-in, you're still screwed by it. That's not going to change just because it's optional.

And my point about template constraint condition proliferation holds even with the current implementation. Anyone choosing to try and put all of the sub-constraints in the top-level constraint has a maintenance problem. Sure, you can choose not to do that and let the user see errors from within the template when they use a type that fails the template constraint of a function being called and thus avoid the constraint proliferation (which then causes its own problems due to how that's more annoying to deal with when you run into it), but the problem is still there. If you opt-in to putting everything in the top-level template constraints, you will have a maintenance issue.

The fact that you can choose what you do or don't put in your template constraints (or  that you could choose whether to use the new paradigm/feature that you're proposing) doesn't fix the problem that going that route causes maintenance issues. That fundamental problem still remains. All it means is that you can choose whether you want to cause yourself problems by going that route, not that they're necessarily a good idea.

- Jonathan M Davis
July 26, 2015
On Sunday, 26 July 2015 at 02:15:20 UTC, jmh530 wrote:
> On Sunday, 26 July 2015 at 00:31:48 UTC, Jonathan M Davis wrote:
>> On Friday, 24 July 2015 at 01:09:19 UTC, H. S. Teoh wrote:
>>
>> The second issue that I see with your suggestion is basically what Walter is saying the problem is. Even if we assume that we _do_ want to put all of the requirements for foo - direct or indirect - in its template constraint, this causes a maintenance problem. For instance, if foo were updated to call another function
>>
>
> I think from your post I finally understand what Walter was getting at.
>
> Not sure if this simplifies things, but what if instead you do something like
>
> void foo(T)(T t)
> 	if (__traits(compiles, bar(t) && __traits(compiles, baz(t)))
> {
>     ...
>     auto b = bar(t);
>     ...
>     auto c = baz(t);
>     ...
> }
>
> This only really works in the case where it's obvious that you are calling some bar(t). It might not work more generally...
>
> Anyway, in your next example, you have additional layers with some more condn!T constraints. If instead you just have more __traits(compiles, x) for whatever templates they are calling, then checking that bar compiles necessarily also tests whether those other functions can compile as well. In this way, you're testing all the constraints at lower levels. So I guess you would still be checking that each constraint works, but at the highest level you only have to specify that the templates you are calling compile.
>
> In my opinion this is superior (for this case) because if you change bar and baz then you don't have to make changes to foo.
>
> Am I wrong? Is this just another way of doing the same thing?

I suggested the same in response to Walter earlier. It is one way to combat the problem. However, it's really only going to work in basic cases (at least, without getting ugly). What if what you passed to bar wasn't t but was a result from calling a function on t? Or maybe it was a result of calling a function on the return value of a function that was called on t? Or perhaps you passed t through a chain of free functions and ended up with some other type from that, and it doesn't pass bar's template constraint? In order to deal with that sort of thing, pretty soon, you have to put most of the function inside its own constraint. It's _far_ cleaner in general to just be putting the sub-constraints in the top-level constraint - e.g. maybe all that it means is using isForwardRange and hasLength instead of just isInputRange rather than putting a whole chain of function calls inside of __traits(compiles, ...) test in the template constraint. It just gets ugly quickly to try and get it to work for you automatically by putting the calls you're making in the constraint so that the actual constraint conditions are inferred.

The other problem is that if you're putting all of those __traits(compiles, ...) tests in template constraints rather than putting the sub-constraints in there, it makes it a lot more of a pain for the user to figure out why they're failing the constraint. The constraint for what's failing in __traits(compiles, ...) isn't shown, whereas it would be if you just let it get past the template constraint and fail the sub-constraint at the point where that function is being called, you'd see the actual condition that's failing. So, as annoying as it would be, it would actually be easier to figure out what you were doing wrong. Also, if you really didn't put the sub-constraint in the top-level constraint at all, then the constraint is split out so that when you get a failure at the top-level, you see only the stuff that the function requires directly, and when you get a failure internally, you see it the condition that that function requires and can see that separately. So, instead of having to figure out which part of condition1 && condition2 is failing, you know which it is, because the conditions are tested in separate places.

I suspect that the best way to go with this is that a template constraint only require the stuff that a function uses directly  and let the constraints on any functions being called internally report their own errors and then have the compiler provide really good error messages to make that sane. Then it can be a lot clearer what condition you're failing when you call the function with a bad argument. But we need to improve the error messages further if we want to go that way.

The other alternative would be to just make a best faith effort to put all of the sub-constraints in the top-level constraint initially and then have better error messages for when the constraint is incomplete due to a change to a function being called. But that would still require better error messages (which is the main problem with the other suggestion), and it actually has the problem that if a function being called has its constraint lessened (rather than made more strict) such that your outer function could then accept more types of arguments, if you put all of the sub-constraints at the top-level, then it won't accept anything more until you realize that the sub-constraints have changed and update the top-level constraint.

Right now, we're more or less living with the second option, but if we can get the error messages to be good enough, I think that the first option is actually better. But either way, we need to find ways to improve the error messages inside of templates to reduce the need to look at their source code when a template constraint doesn't prevent an argument being used with it that doesn't compile with it (particularly in the cases where it's due to a function being called rather than that function itself having a bug).

- Jonathan M Davis
July 26, 2015
On Sunday, 26 July 2015 at 04:34:55 UTC, Jonathan M Davis wrote:
>
> I suggested the same in response to Walter earlier. It is one way to combat the problem. However, it's really only going to work in basic cases (at least, without getting ugly). What if what you passed to bar wasn't t but was a result from calling a function on t? Or maybe it was a result of calling a function on the return value of a function that was called on t? Or perhaps you passed t through a chain of free functions and ended up with some other type from that, and it doesn't pass bar's template constraint? In order to deal with that sort of thing, pretty soon, you have to put most of the function inside its own constraint. It's _far_ cleaner in general to just be putting the sub-constraints in the top-level constraint - e.g. maybe all that it means is using isForwardRange and hasLength instead of just isInputRange rather than putting a whole chain of function calls inside of __traits(compiles, ...) test in the template constraint. It just gets ugly quickly to try and get it to work for you automatically by putting the calls you're making in the constraint so that the actual constraint conditions are inferred.
>
> {snip}

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).
July 26, 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
July 26, 2015
On 25/07/2015 9:48 PM, Walter Bright wrote:

> Unfortunately, Bruce Eckel's seminal article on it
> http://www.mindview.net/Etc/Discussions/CheckedExceptions has
> disappeared. Eckel is not a Java code monkey, he wrote the book Thinking
> In Java
> http://www.amazon.com/gp/product/0131002872/
>


https://web.archive.org/web/20150515072240/http://www.mindview.net/Etc/Discussions/CheckedExceptions
July 26, 2015
On Sunday, 26 July 2015 at 00:18:14 UTC, Walter Bright wrote:
> On 7/25/2015 3:59 PM, deadalnix wrote:
>> On Saturday, 25 July 2015 at 12:05:12 UTC, Andrei Alexandrescu wrote:
>>> Well at least all paths must be compiled. You wouldn't ship templates that
>>> were never instantiated just as much as you wouldn't ship any code without
>>> compiling it. We've had a few cases in Phobos a while ago of templates that
>>> were never instantiated, with simple compilation errors when people tried to
>>> use them. -- Andrei
>>
>> That is an instance of happy case testing. You test that what you expect to work
>> work. You can't test that everything that is not supposed to work do not, or
>> that you don't rely on a specific behavior of the thing you are testing.
>>
>
> Um, testing all paths is not happy case testing.

You test all execution path, not all "instantiation path". Consider this, in a dynamically typed language, you can have a function that accept a string and do something with it. You can write unit tests to check it does the right thing with various strings and make sure it execute all path.

Yet, what happen when it is passed an int ? a float ? an array ? an object ? Probably random shit.

Same here, but at instantiation time.
July 26, 2015
>>I think the ability to express an interface without buying into inheritance is the >>right move. The alternative in D is specifying the behavior as a template and >>verifying the contract in a unittest for the type.

>>Rust only has structs. I'm not as familiar with them so it's not as clear how they >>overlap with D's structs and classes. It seems like you can put Rust structs on the >>stack or heap.

What some people call "interface inheritance" in Rust is not inheritance in the sense of OOP whewre an inherited method can be overwritten. Rust is here the same as Go. Some people sometimes get confused and don't see that without the possibility of overwriting an inherited method delegation applies and not inheritance. This is also explained in this blog: http://objectscape.blogspot.de/search/label/Go See the section titled "Inheritance".

-- Bienlein
July 26, 2015
On Sunday, 26 July 2015 at 01:33:09 UTC, Enamex wrote:
> On Thursday, 23 July 2015 at 15:03:56 UTC, Dicebot wrote:
>> At the same time one HUGE deal breaker with rust traits that rarely gets mentioned is the fact that they are both constraints and interfaces at the same time:
>>
>> ...
>>
>> It kills all the necessity for hacks like RangeObject and is quite a salvation once you get to defining dynamic shared libraries with stable ABI.
>>
>> This is probably my most loved feature of Rust.
>
> Sorry, I don't quite get this. How is the most loved feature of Rust (that interfaces are also constraints for generics), a *deal breaker*?

I have just checked the dictionary and it simply a matter of being having terrible English and using this phrase wrong all the time :) It was supposed to mean something like "feature that makes crucial (positive) difference"
July 26, 2015
On Saturday, 25 July 2015 at 21:20:28 UTC, Walter Bright wrote:
> On 7/25/2015 6:35 AM, Dicebot wrote:
>> If compiler would actually show 0 coverage for non-instantiated lines, than
>> automatic coverage control check in CI would complain and code would never be
>> shipped unless it gets covered with tests (which check the semantics). Your are
>> putting it totally backwards.
>
> A good case. https://issues.dlang.org/show_bug.cgi?id=14825

Thanks!