November 04, 2017
On Sat, Nov 04, 2017 at 09:38:42AM -0600, Jonathan M Davis via Digitalmars-d wrote:
> On Saturday, November 04, 2017 15:27:39 Ola Fosheim Grøstad via Digitalmars- d wrote:
> > On Saturday, 4 November 2017 at 14:12:08 UTC, Adam D. Ruppe wrote:
> > > On Saturday, 4 November 2017 at 13:59:39 UTC, Jonathan M Davis
> > >
> > > wrote:
> > >> I'm very much of the opinion that proper unit tests pretty much eliminate the need for out contracts.
> > >
> > > I think that sqrt example is just bad. Out contracts shouldn't be testing specific values, but rather ranges or nullness or other such things that arguably should be part of the return type, but just don't fit in the type system for whatever reason.
> >
> > No, Jonathan is correct. The postcondition should be able to access values as they were stated in the precondition.
> 
> In principle, that would be nice, but in practice, it's not really feasible.  In the general case, there's no way to save the state of the parameter at the beginning of the function call (you could with some types, but for many types, you couldn't). IIRC, it's been discussed quite a bit in the past, and there are just too many problems with it.
[...]

This isn't as bad as it sounds. If you really cared for out-contracts being able to access the original state of the parameters, just make the parameters const.  In the cases where you can't make them const, out-contracts are probably impractical anyway -- independently of how D implements them, if your function is going to mutate parameters then the only way the out-contract would work is if a copy of the parameters were made.  But if you're already going to do that, might as well just make the parameters const and do the copy inside the function body.

But as Adam said, out-contracts aren't really intended for semantic enforcement, but they are more like extensions of the type system. E.g., asserting that a returned pointer is never null, or that a returned value is never negative, or stays within some range of values, etc.. Basically, stuff the caller can assume about the return value as far as the set of possible values is concerned.  For most non-trivial functions, semantic enforcement requires you to basically reimplement the function body, which is kinda pointless.  If you want semantic enforcement, that's what unittests are for.


T

-- 
Caffeine underflow. Brain dumped.
November 04, 2017
On Saturday, 4 November 2017 at 15:38:42 UTC, Jonathan M Davis wrote:
> In principle, that would be nice, but in practice, it's not really feasible. In the general case, there's no way to save the state of the parameter at the beginning of the function call (you could with some types, but for many types, you couldn't). IIRC, it's been discussed quite a bit in the past, and there are just too many problems with it.

It is obviously possible for a language that has been designed for contracts. Whether it is practical is a matter of optimization in my view…

Anyway, without having such a focus it makes little sense to say that a language supports programming by contracts or pre/post conditions. It is more of a syntactical construct in D. Although class invariants are still useful in debugging.

C++ is getting pre/post conditions I think. Not sure if it will be a plain syntactical construct like in D or something more advanced.

November 04, 2017
On Saturday, 4 November 2017 at 15:27:39 UTC, Ola Fosheim Grøstad wrote:
> No, Jonathan is correct. The postcondition should be able to access values as they were stated in the precondition.

Yes, they should be able to access values, but D's limitations on this doesn't make them useless or replaced by unittests. They do a different job than unittests.
November 04, 2017
On Saturday, 4 November 2017 at 06:08:22 UTC, Jonathan M Davis wrote:
> And even if you did have access to the input, some functions consume their input without any way to save it (e.g. an input range that isn't a forward range)
> 
> [...]
> 
> - Jonathan M Davis

True. I had (strongly) pure functions in my mind and I didn't realize that things don't work as smooth in the impure case.
November 04, 2017
On Saturday, 4 November 2017 at 15:38:42 UTC, Jonathan M Davis wrote:
> On Saturday, November 04, 2017 15:27:39 Ola Fosheim Grøstad via Digitalmars- d wrote:
>> On Saturday, 4 November 2017 at 14:12:08 UTC, Adam D. Ruppe wrote:
>> > On Saturday, 4 November 2017 at 13:59:39 UTC, Jonathan M Davis
>> >
>> > wrote:
>> >> [...]
>> >
>> > I think that sqrt example is just bad. Out contracts shouldn't be testing specific values, but rather ranges or nullness or other such things that arguably should be part of the return type, but just don't fit in the type system for whatever reason.
>>
>> No, Jonathan is correct. The postcondition should be able to access values as they were stated in the precondition.
>
> In principle, that would be nice, but in practice, it's not really feasible. In the general case, there's no way to save the state of the parameter at the beginning of the function call (you could with some types, but for many types, you couldn't). IIRC, it's been discussed quite a bit in the past, and there are just too many problems with it.
>
> - Jonathan M Davis

Yeah, I can see some of the problems that would arise from that. I think out contracts can work well for @safe strongly pure functions, but IIRC most functions in Phobos aren't strongly pure. Too bad. :(
November 04, 2017
On Saturday, 4 November 2017 at 16:57:50 UTC, Adam D. Ruppe wrote:
> On Saturday, 4 November 2017 at 15:27:39 UTC, Ola Fosheim Grøstad wrote:
>> No, Jonathan is correct. The postcondition should be able to access values as they were stated in the precondition.
>
> Yes, they should be able to access values, but D's limitations on this doesn't make them useless or replaced by unittests. They do a different job than unittests.

But then they do a job that subtyping does better…? So why do this with "contracts"?

Anyhow, I think the ideal for contracts in terms of debugging is to express postconditions in terms of public interface rather than internal representation, e.g. for a stack push:

oldthis.size() + 1 == newthis.size()  // or length() or whatever the interface provides

Then hammer the interface in unit tests rather than writing the explicit test in the unit test itself. (e.g. the unit test does not have to know the conditions, just explore a wide range of configurations).


November 04, 2017
On 04.11.2017 10:12, Adam D. Ruppe wrote:
> On Saturday, 4 November 2017 at 13:59:39 UTC, Jonathan M Davis wrote:
>> I'm very much of the opinion that proper unit tests pretty much eliminate the need for out contracts.
> 
> I think that sqrt example is just bad.

It is indeed bad, because it is buggy.
(The out contract may overflow.)

> Out contracts shouldn't be testing specific values,

It's not testing specific values. It is testing for input-dependent properties of the output (which in this case happen to pinpoint the output exactly for each input).

> but rather ranges or nullness or other such things that arguably should be part of the return type, but just don't fit in the type system for whatever reason.

I don't see how that is in contrast to the given example. Clearly, the conditions that it is testing for should have been represented in the type system. Then the example would not compile. Also, one would not be able to complain about runtime overhead.
November 05, 2017
On Saturday, 4 November 2017 at 15:38:42 UTC, Jonathan M Davis wrote:
> In principle, that would be nice, but in practice, it's not really feasible. In the general case, there's no way to save the state of the parameter at the beginning of the function call (you could with some types, but for many types, you couldn't). IIRC, it's been discussed quite a bit in the past, and there are just too many problems with it.
>
> - Jonathan M Davis

What about making definitions in "in" block visible in "out" block?

void fun(int par)
in {
    int saved = par;
}
body {
    par = 7;
}
out {
    writeln(saved);
}
November 05, 2017
On Sunday, November 05, 2017 16:25:16 MrSmith via Digitalmars-d wrote:
> On Saturday, 4 November 2017 at 15:38:42 UTC, Jonathan M Davis
>
> wrote:
> > In principle, that would be nice, but in practice, it's not really feasible. In the general case, there's no way to save the state of the parameter at the beginning of the function call (you could with some types, but for many types, you couldn't). IIRC, it's been discussed quite a bit in the past, and there are just too many problems with it.
> >
> > - Jonathan M Davis
>
> What about making definitions in "in" block visible in "out" block?
>
> void fun(int par)
> in {
>      int saved = par;
> }
> body {
>      par = 7;
> }
> out {
>      writeln(saved);
> }

That would help, and I think that that it was discussed previously, but it would mean that in and out were in the same scope, which would be a breaking change. There may have been other reasons why it would be problematic, but I don't recall. There's certainly nothing stopping the creation of a DIP on the topic though if someone feels strongly enough about it to put in the time and effort.

I'm sure that there are various things that could be done to improve contracts (and DIP 1009 is one such attempt), but I've mostly given up on it. I don't think that they provide enough value in the first place. Aside from issues involving contracts and inheritance, in contracts can just be put in the function body, making them kind of pointless, and for the most part, unit tests test anything that I would consider testing in an out contract - and generally do it better. That combined with the issue with invariant and void initialized objects, and as far as I'm concerned, contracts are pretty pointless, much as they're well-intentioned. I almost never use them.

Probably the one thing that would make me change my tune on in contracts would be if it were somehow changed so that whether they were run depended on whether the caller was compiled with assertions enabled rather than whether the function being called was compiled with assertions, since with in contracts, it's really the caller that you're testing, not the function itself. But short of that, I doubt I'll ever do much with them.

However, one thing that annoys me enough that I might do something about it at some point is how invariant is broken with void initialized objects, since unlike in and out contracts, doing what invariant does without using the feature is a royal pain. So, at some point, I may create a DIP to deal with that (probably by providing a way to explicitly indicate that you want the invariant skipped at the callsite), but it hasn't been a particularly high priority for me, so I haven't done anything with it.

- Jonathan M Davis

November 05, 2017
On Saturday, November 04, 2017 14:12:08 Adam D. Ruppe via Digitalmars-d wrote:
> On Saturday, 4 November 2017 at 13:59:39 UTC, Jonathan M Davis
>
> wrote:
> > I'm very much of the opinion that proper unit tests pretty much eliminate the need for out contracts.
>
> I think that sqrt example is just bad. Out contracts shouldn't be testing specific values, but rather ranges or nullness or other such things that arguably should be part of the return type, but just don't fit in the type system for whatever reason.
>
> Consider this:
>
> class A {
>          A clone()
>          out(result) { assert(result !is null); }
>          body {
>                  return new A();
>          }
> }
> class B : A {
>          override B clone() {
>                  return new B();
>          }
> }
>
> void main() {
>          A a = new B();
>          A c = a.clone();
> }
>
>
> The `clone` method defined in the base class arguably out to return a NotNull!A, encoding that contract in the type, but we can't really do that with D without breaking the inheritance - where B is statically defined to return B too, since NotNull!B isn't covariant to NotNull!A the way B is covariant to A. (At least not with any techniques I can think of at this time.)
>
> So you can't encode it in the return type... but you can encode it in the out contract for a runtime check.
>
> It also clearly documents that the subclasses must not return null (and can add their own restrictions to further subclasses). One problem I have with some of D's contracts right now is that they are convoluted, ugly code that make for passable internal unit tests, you can't reasonably display as documentation as part of the interface.
>
> But regardless, I say you should not think of out as tests. Think of it as type system extensions.

It's an interesting idea, but I use classes so rarely in D that I wouldn't hit this use case often enough to even think about it. In fact, at the moment, I don't think that I've _ever_ written a clone function in D, and I probably wouldn't even bother with null checks (in general, not just with clone functions), since the program will just segfault on me if I screw that up, so it's not like it won't be caught, and with the way I write code, I almost never have problems with null objects except in cases where I forgot to initialize something, and that gets found _really_ fast in testing.

- Jonathan M Davis