July 26, 2015
On 7/25/2015 3:19 PM, Guillaume Chatelet wrote:
> On Saturday, 25 July 2015 at 20:48:06 UTC, Walter Bright wrote:
>> On 7/25/2015 11:40 AM, Tobias Müller wrote:
>>> I'm not convinced at all that checked exceptions (as implemented in Java,
>>> not C++) don't work.
>>>
>>> My suspicion is that the usual Java code monkey is just too sloppy to care
>>> and thus sees it more as a nuisance rather than the help that it is.
>>
>> 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/
>
> This ?
> http://www.artima.com/intv/handcuffs.html

No, that's Anders.
July 26, 2015
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.
July 26, 2015
On Friday, 24 July 2015 at 01:09:19 UTC, H. S. Teoh wrote:
> I have trouble thinking of a template function that's actually *correct* when its sig constraints doesn't specify what operations are valid on the incoming type. Can you give an example?
>
> If such code is wrong, I'd say the language *should* reject it.

I see two issues here, both of which relate to maintenance. The first one is that if the language were actually able to check that you missed a requirement in your template constraint (like you're suggesting) and then give you an error, that makes it way easier to break valid code. Take code like this, for example

auto foo(T)(T t)
    if(cond1!T && cond2!T)
{
    ...
    auto b = bar(t);
    ...
}

auto bar(T)(T t)
    if(cond2!T)
{
    ...
}

foo calls bar, and it does have all of bar's constraints in its own constraints so that you don't end up with a compilation error when you pass foo something that doesn't work with bar. Now, imagine if bar gets updated, and now its

auto bar(T)(T t)
    if(cond2!T && cond3!T)
{
    ...
}

but foo's constraint isn't updated (e.g. because foo is in a different library or program that depends no bar, so the person who updates bar isn't necessarily the same person who maintains foo). If the compiler then caught the fact that foo didn't check all of bar's constraints and gave an error, that would alert anyone using foo that foo needed to be updated, but it would also mean that foo would no longer compile, when it's quite possible that the argument passed to foo does indeed pass bar's template constraint and will work just fine with foo. So, working code no longer compiles when there's no technical reason why it couldn't continue to work. Presumably, once the maintainer of foo finds out about this, they'll update foo, and the problem will be fixed, but it still means that every time that the template constraint for bar is adjusted at all, every template that uses it risks breaking if the compiler insists that those templates check all of bar's constraints.

So, yes. it does help ensure that users of foo don't end up with error messages inside of foo thanks to foo's template constraint not listing everything that it actually requires, but it also breaks a lot of code when template constraints change when the code itself will often work just fine as-is (particularly since the change to bar that required a change to its template constraint would usually be a change to its implementation and not what it did, since if you changed what it did, everyone that used it would be broken anyway). Code that's actually broken by the change to bar will fail bar's new template constraint even if the compiler doesn't complain about foo (or any other function) not having updated its constraint, and it'll still get caught. The error might not be as nice, since it'll often be in someone else's templated code, but it'll still be an error, and it'll still tell you what's failing. So, with the current state of affairs, only code that's actually broken by a change to bar's template constraint would be broken and not everyone, whereas what you're suggesting would break all code that used bar that didn't happen to also check the same thing that bar was now checking for.

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

auto foo(T)(T t)
    if(cond1!T && cond2!T && cond3!T && cond4!T)
{
    ...
    auto b = bar(t);
    ...
    auto c = baz(t);
    ...
}

auto bar(T)(T t)
    if(cond2!T && cond3!T)
{
    ...
}

auto baz(T)(T t)
    if(cond1!T && cond4!T)
{
    ...
}

you now have to update foo. Okay. That's not a huge deal, but now you have two functions that you're using within foo whose template constraints need to be duplicated in foo's template constraint. And ever function that _they_ call ends up affecting _their_ template constraints and then foo in turn.

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)
{
    ...
}

So, foo's template constraint potentially keeps getting nastier and nastier thanks to indirect calls that it's making. Now, often there's going to be a large overlap between these constraints (e.g. because they're all range-based functions using isInputRange, isForwardRange, hasLength, etc.), so maybe foo's constraint doesn't get that nasty. But where you still have a maintenance problem even if that's the case is if a function that's being called indirectly adds something to its template constraint, then everything up the chain has to add it if you want to make sure that foo gets no compilation internally due to it failing to pass a template constraint of something that it's calling. So, if wolf ends up with a slightly more restrictive constraint in the next release, then every templated function on the planet which used it - directly or indirectly - would need to be updated. And much of that code could be maintained by someone other than the person who made the change to wolf, and much of it could be code that they've don't even know exists. So, if we're really trying to put everything that a function requires - directly or indirectly - in its template constraint, we potentially have a huge maintenance problem here once you start having templated functions call other templated functions - especially if any of these functions are part of a library that's distributed to others. But even if it's just your own code base, a slight adjustment to a template constraint could force you to change a _lot_ of the other template constraints in your code.

So, while I definitely agree that it's nicer from the user's standpoint when the template constraint checks everything that the function requires - directly or indirectly - I think that we have a major maintenance issue in the making here if that's what we insist on. Putting all of the sub-constraints in the top-level constraint - especially with multiple levels of templated functions - simply doesn't scale well, even if it's desirable. Maybe some kind of constraint inference would solve the problem. I don't know. But I think that it is a problem, and it's one that we haven't really recognized yet.

At this point, even if we're going to try and have top-level template constraints explicitly contain all of the constraints of the templates that they use - directly or indirectly - I think that we really need to make sure that the error messages from within templated code are as good as we can make them, because there's no way that all template constraints are going to contain all of their sub-constraints as code is changed over time, not unless the constraints are fairly simple and looking for the same stuff.

Fortunately, the error messages are a lot better than they used to be, but if we can improve them sufficiently, then it becomes less critical to make sure that all sub-constraints be in the top-level constraint, and it makes it a lot more palatable when sub-constraints are missed.

But as I said in the first part, I really don't think that detecting missing constraints and giving errors is a good solution. It'll just break more code that way. Rather, what we need is to either find a way to infer the sub-constraints into the top-level constraint and/or to provide really good error messages when errors show up inside templated code, because a constraint didn't check enough.

- Jonathan M Davis
July 26, 2015
On Saturday, 25 July 2015 at 22:54:07 UTC, deadalnix wrote:
> This unitest argument is becoming ridiculous. Unless some strong argument is brought to the table that this differs from the "dynamic typing is not a problem if you write unitest" we we all should know is bogus at this point, it can't be taken seriously.

+1000
July 26, 2015
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*?
July 26, 2015
On Saturday, 25 July 2015 at 09:14:04 UTC, Jonathan M Davis wrote:
> . . .
> auto foo(alias pred, R)(R r)
>     if(testPred!pred && isInputRange!R && !isForwardRange!R)
> {}
>
> auto foo(alias pred, R)(R r)
>     if(testPred!pred && isForwardRange!R)
> {}
>
> and be turning it into something like
>
> template foo(alias pred)
>     if(testPred!pred)
> {
>     auto foo(R)(R r)
>         if(isInputRange!R && !isForwardRange!R)
>     {}
>
>     auto foo(R)(R r)
>         if(isForwardRange!R)
>     {}
> }
> . . .
> - Jonathan M Davis

The example(s) is confusing me. `foo!(first)(second);` isn't really an alternative to `foo(first, second);`. Am I misreading something?
July 26, 2015
On Sunday, 26 July 2015 at 00:31:48 UTC, Jonathan M Davis wrote:
> I see two issues here, both of which relate to maintenance. The first one is that if the language were actually able to check that you missed a requirement in your template constraint (like you're suggesting) and then give you an error, that makes it way easier to break valid code. Take code like this, for example
>
> auto foo(T)(T t)
>     if(cond1!T && cond2!T)
> {
>     ...
>     auto b = bar(t);
>     ...
> }
>
> auto bar(T)(T t)
>     if(cond2!T)
> {
>     ...
> }
>
> foo calls bar, and it does have all of bar's constraints in its own constraints so that you don't end up with a compilation error when you pass foo something that doesn't work with bar. Now, imagine if bar gets updated, and now its
>
> auto bar(T)(T t)
>     if(cond2!T && cond3!T)
> {
>     ...
> }
>
> but foo's constraint isn't updated (e.g. because foo is in a different library or program that depends no bar, so the person who updates bar isn't necessarily the same person who maintains foo). If the compiler then caught the fact that foo didn't check all of bar's constraints and gave an error, that would alert anyone using foo that foo needed to be updated, but it would also mean that foo would no longer compile, when it's quite possible that the argument passed to foo does indeed pass bar's template constraint and will work just fine with foo. So, working code no longer compiles when there's no technical reason why it couldn't continue to work. Presumably, once the maintainer of foo finds out about this, they'll update foo, and the problem will be fixed, but it still means that every time that the template constraint for bar is adjusted at all, every template that uses it risks breaking if the compiler insists that those templates check all of bar's constraints.


I think one of the key points, is that it would be opt-in. Current constraints would continue to work as they do now. Only templates that chose to use the new tighter constraints would have this "problem". They would be separate from current constraints (with a separate syntax). Code using current constraints would not stop working if a template that used the new constraints.

It makes sense when you consider how current constraints work now, they basically say "If you can't do X then fail" but they say nothing about if it can do extra things in addition to X.

So for example in the following code I will use the specialization syntax to signify one of the NEW constraints and the regular "if" syntax for the current constraints.


void foo(T)(T x) if(cond1!T) // OLD constraint
{
     bar(x);
}

void bar(T : cond2)(T x) // NEW constraint
{
     ...
}

Would still work, and would only fail when foo gets instantiated with a type that does not pass both cond1 and cond2. Which makes perfect sense in the context of how the current constraints work. They only test if you can do something, they don't care if you can do extra things. There would be no need to update the constraints on foo.

> So, yes. it does help ensure that users of foo don't end up with error messages inside of foo thanks to foo's template constraint not listing everything that it actually requires, but it also breaks a lot of code when template constraints change when the code itself will often work just fine as-is (particularly since the change to bar that required a change to its template constraint would usually be a change to its implementation and not what it did, since if you changed what it did, everyone that used it would be broken anyway). Code that's actually broken by the change to bar will fail bar's new template constraint even if the compiler doesn't complain about foo (or any other function) not having updated its constraint, and it'll still get caught. The error might not be as nice, since it'll often be in someone else's templated code, but it'll still be an error, and it'll still tell you what's failing. So, with the current state of affairs, only code that's actually broken by a change to bar's template constraint would be broken and not everyone, whereas what you're suggesting would break all code that used bar that didn't happen to also check the same thing that bar was now checking for.

With what I said about opt-in I think every thing above is null.

The key advantage to the new constraints would be that it constrains the type to only do what the constraints say. As opposed to being able to do anything in addition to what the constraints say.

> 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
>
> auto foo(T)(T t)
>     if(cond1!T && cond2!T && cond3!T && cond4!T)
> {
>     ...
>     auto b = bar(t);
>     ...
>     auto c = baz(t);
>     ...
> }
>
> auto bar(T)(T t)
>     if(cond2!T && cond3!T)
> {
>     ...
> }
>
> auto baz(T)(T t)
>     if(cond1!T && cond4!T)
> {
>     ...
> }
>
> you now have to update foo. Okay. That's not a huge deal, but now you have two functions that you're using within foo whose template constraints need to be duplicated in foo's template constraint. And ever function that _they_ call ends up affecting _their_ template constraints and then foo in turn.
>
> 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)
> {
>     ...
> }
>
> So, foo's template constraint potentially keeps getting nastier and nastier thanks to indirect calls that it's making. Now, often there's going to be a large overlap between these constraints (e.g. because they're all range-based functions using isInputRange, isForwardRange, hasLength, etc.), so maybe foo's constraint doesn't get that nasty. But where you still have a maintenance problem even if that's the case is if a function that's being called indirectly adds something to its template constraint, then everything up the chain has to add it if you want to make sure that foo gets no compilation internally due to it failing to pass a template constraint of something that it's calling. So, if wolf ends up with a slightly more restrictive constraint in the next release, then every templated function on the planet which used it - directly or indirectly - would need to be updated. And much of that code could be maintained by someone other than the person who made the change to wolf, and much of it could be code that they've don't even know exists. So, if we're really trying to put everything that a function requires - directly or indirectly - in its template constraint, we potentially have a huge maintenance problem here once you start having templated functions call other templated functions - especially if any of these functions are part of a library that's distributed to others. But even if it's just your own code base, a slight adjustment to a template constraint could force you to change a _lot_ of the other template constraints in your code.

Again, with it being opt-in, this problem is not as bad as what you say. Only templates that choose to use the new constraints would need to do the sort of maintenance that you say.

Also this problem is the same as normal type systems experience! If some low level function needs something new out of type T then the same problem will arise. Its not a problem there so it should not be a problem here.

> So, while I definitely agree that it's nicer from the user's standpoint when the template constraint checks everything that the function requires - directly or indirectly - I think that we have a major maintenance issue in the making here if that's what we insist on. Putting all of the sub-constraints in the top-level constraint - especially with multiple levels of templated functions - simply doesn't scale well, even if it's desirable. Maybe some kind of constraint inference would solve the problem. I don't know. But I think that it is a problem, and it's one that we haven't really recognized yet.

Again opt-in.

> At this point, even if we're going to try and have top-level template constraints explicitly contain all of the constraints of the templates that they use - directly or indirectly - I think that we really need to make sure that the error messages from within templated code are as good as we can make them, because there's no way that all template constraints are going to contain all of their sub-constraints as code is changed over time, not unless the constraints are fairly simple and looking for the same stuff.
>
> Fortunately, the error messages are a lot better than they used to be, but if we can improve them sufficiently, then it becomes less critical to make sure that all sub-constraints be in the top-level constraint, and it makes it a lot more palatable when sub-constraints are missed.

Better errors are of course better.

> But as I said in the first part, I really don't think that detecting missing constraints and giving errors is a good solution. It'll just break more code that way. Rather, what we need is to either find a way to infer the sub-constraints into the top-level constraint and/or to provide really good error messages when errors show up inside templated code, because a constraint didn't check enough.
>
> - Jonathan M Davis

Key point is opt-in.

July 26, 2015
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?
July 26, 2015
On Sunday, 26 July 2015 at 01:55:12 UTC, Enamex wrote:
> On Saturday, 25 July 2015 at 09:14:04 UTC, Jonathan M Davis wrote:
>> . . .
>> auto foo(alias pred, R)(R r)
>>     if(testPred!pred && isInputRange!R && !isForwardRange!R)
>> {}
>>
>> auto foo(alias pred, R)(R r)
>>     if(testPred!pred && isForwardRange!R)
>> {}
>>
>> and be turning it into something like
>>
>> template foo(alias pred)
>>     if(testPred!pred)
>> {
>>     auto foo(R)(R r)
>>         if(isInputRange!R && !isForwardRange!R)
>>     {}
>>
>>     auto foo(R)(R r)
>>         if(isForwardRange!R)
>>     {}
>> }
>> . . .
>> - Jonathan M Davis
>
> The example(s) is confusing me. `foo!(first)(second);` isn't really an alternative to `foo(first, second);`_?_. Am I misreading something?

No. Yes. `first` is a compile time parameter and can be anything. In this case it defines a type (in examples T is used for generic type, and R for a range). but it can be anything, a type literal i.e. foo!(size_t a)(Bar b); , it can be another symbol ( a function, class or even another template (with is own set of args)! it can be varaidic as well i.e. foo!(T...)(some_args)

Second is the set of runtime parameters, which can be of types defined in the compile time args.
July 26, 2015
On 7/25/2015 3:28 PM, deadalnix wrote:
> Also, argument from ignorance is hard to maintain when the thread is an actual
> feedback from experience.

You say that interfaces do the same thing. So please show how it's done with the example I gave:

    int foo(T: hasPrefix)(T t) {
       t.prefix();    // ok
       bar(t);        // error, hasColor was not specified for T
    }

    void bar(T: hasColor)(T t) {
       t.color();
    }