digitalmars.D - Suggested Change to Contract Syntax
- FatalCatharsis (78/78) Mar 10 2016 I am very new to D so I apologize if I'm very uninformed. I'm
- Xinok (29/36) Mar 10 2016 The most "elegant" solution I can think of is to move the
- Adam D. Ruppe (10/17) Mar 10 2016 Not quite because contracts are inherited. For a child class,
- Jonathan M Davis (29/31) Mar 10 2016 IMHO, at this point, inheritance is the only reason they're worth
- jmh530 (42/45) Mar 10 2016 I created a simple example to understand your point about
- Jonathan M Davis (112/158) Mar 10 2016 You're not using in or out contracts here at all, so so of
- jmh530 (5/9) Mar 10 2016 Sigh...my point was that I was replacing the in/out contracts
- Jonathan M Davis (8/19) Mar 10 2016 Sure, but if you're not using in/out contracts with a class,
- jmh530 (35/42) Mar 10 2016 If I'm understanding you correctly, you could still get the same
- Jonathan M Davis (37/56) Mar 10 2016 Yeah, you can do something like that and get it to work, but it
- FatalCatharsis (13/21) Mar 10 2016 This is a very good point. I was already using unittests and I
- Jonathan M Davis (54/64) Mar 10 2016 Well, if you had something like
- Jacob Carlborg (9/34) Mar 11 2016 Another advantage of the contracts would be if the caller would be
- Jonathan M Davis (25/30) Mar 11 2016 The biggest problem with associating the contract with the source
I am very new to D so I apologize if I'm very uninformed. I'm learning D by porting a big (awful) c++ project to D so that I may take advantage of all the lovely testing and QA features. I am currently moving a class over that I have which represents a 2 float vector and I've run into a problem with contracts. Here is a short excerpt: struct Vector2(T) { T x = 0; T y = 0; Vector2!T opBinary(string op)(const ref Vector2!T rhs) if(op == "+" || op == "-" || op == "*" || op == "/") in { T prevx = this.x; T prevy = this.y; } out { static if(isFloatingPoint!T) { assert(mixin("approxEqual(this.x, prevx"~op~"rhs.x)")); assert(mixin("approxEqual(this.y, prevy"~op~"rhs.y)")); } else { assert(mixin("this.x == (prevx"~op~"rhs.x)")); assert(mixin("this.y == (prevy"~op~"rhs.y)")); } } body { Vector2!T ret; mixin("ret.x = this.x"~op~"rhs.x;"); mixin("ret.y = this.y"~op~"rhs.y;"); return ret; } } this example obviously does not compile. What I am attempting to do is store the initial value of the x and y before the body is run, so that I can use those values in the post condition. I've read around and asked a question on stackoverflow, and it seems that there is no facility for this. I am NOT very knowledgable in compilation, but I do get the gist of it. I was hoping to suggest a syntax change and get some opinions on it. What if contracts were done like this: struct Vector2(T) { T x = 0; T y = 0; Vector2!T opBinary(string op)(const ref Vector2!T rhs) if(op == "+" || op == "-" || op == "*" || op == "/") contract { T prevx; T prevy; in { prevx = this.x; prevy = this.y; } out { static if(isFloatingPoint!T) { assert(mixin("approxEqual(this.x, prevx"~op~"rhs.x)")); assert(mixin("approxEqual(this.y, prevy"~op~"rhs.y)")); } else { assert(mixin("this.x == (prevx"~op~"rhs.x)")); assert(mixin("this.y == (prevy"~op~"rhs.y)")); } } } body { Vector2!T ret; mixin("ret.x = this.x"~op~"rhs.x;"); mixin("ret.y = this.y"~op~"rhs.y;"); return ret; } } In this example, the contract would represent a function that is invoked on each invocation of the function. in and out would enclose the values prevx and prevy. in would be invoked immediately after, followed by the body, and then followed by out. Syntactically I think this is clear, but I don't know much about the technical implementation. Is this feasible?
Mar 10 2016
On Thursday, 10 March 2016 at 21:07:09 UTC, FatalCatharsis wrote:I am very new to D so I apologize if I'm very uninformed. I'm learning D by porting a big (awful) c++ project to D so that I may take advantage of all the lovely testing and QA features. I am currently moving a class over that I have which represents a 2 float vector and I've run into a problem with contracts. Here is a short excerpt: ...The most "elegant" solution I can think of is to move the contracts into the body of the function itself and wrap them in version(unittest) or version(assert). The pre-contract would be placed at the very start of the function and the post-contract would be wrapped in a scope(exit) or scope(success). Regarding your proposal, I don't think it's necessary to introduce new syntax; a simple change in semantics would suffice. If we simply preserved the body of the pre-contract and made it accessible in the post-contract, then your example would work as is. I'm not sure how the compilers translate the contracts into code, but it's definitely feasible. Code of the form: auto foo() in{ ... } out(result){ ... } body{ ... } Could simply be rewritten as: auto foo() { // paste pre-contract here auto bodyOfFoo() { // paste body here } auto result = bodyOfFoo(); // paste post-contract here return result; }
Mar 10 2016
On Thursday, 10 March 2016 at 22:04:24 UTC, Xinok wrote:I'm not sure how the compilers translate the contracts into code, but it's definitely feasible. Code of the form: auto foo() in{ ... } out(result){ ... } body{ ... } Could simply be rewritten as:Not quite because contracts are inherited. For a child class, either the child OR the parent's in contract needs to pass, but both the child AND parent's out contracts need to pass. The result is that the child in contract might run without the parent in... but then the parent's out will still be run. Will it see uninitialized variables? Or partially run stuff as the in contract throws a swallowed exception half way through? Contracts are most interesting in the case of inheritance and keeping variables between them isn't going to be easy.
Mar 10 2016
On Thursday, 10 March 2016 at 22:39:54 UTC, Adam D. Ruppe wrote:Contracts are most interesting in the case of inheritance and keeping variables between them isn't going to be easy.IMHO, at this point, inheritance is the only reason they're worth having in the language. Without inheritance, in contracts could just as easily be assertions at the top of the function, and while out contracts are certainly easier as they are now rather than having to put a contract at each return statement or use a scope(exit) statement to do the out contracts, you can still do out contracts that way, and honestly, I don't think that out contracts are worth much anyway, since in almost all cases, unit tests do that job already, and it's usually much easier to write test that specific input gives specific output than it is to have a general test at the end of the function. But while in most cases, in and out contracts are trivially done in the function itself, inheritance is a whole other kettle of fish, and having them built into the language solves that problem whereas doing it yourself is actually fairly hard and error-prone. So, for that reason, and that reason alone, I think that having them built into the language is a good thing. There has been some discussion of getting the compiler to catch some stuff based on in contracts, in which case, in contracts would be worth a bit more, but as it stands, I pretty much never use out contracts (because I think that they're useless), and if inheritance isn't involved (which it usually isn't), then I don't even bother with an in contract and just put the assertions in the body and avoid the extra syntax. If/Once we get more features in the compiler which take advantage of contracts, then I'll likely change my tune on that one, but for now, IMHO, they're really only of value in classes. - Jonathan M Davis
Mar 10 2016
On Thursday, 10 March 2016 at 22:57:41 UTC, Jonathan M Davis wrote:IMHO, at this point, inheritance is the only reason they're worth having in the language. [snip]I created a simple example to understand your point about contracts only really mattering for inheritance, but my example is giving assertion errors for the inherited class the same way as the base class. What would I need to do for this issue to become apparent? class A { int a; this(int x) { a = x; } int foo(int x) { assert(x != 0); scope(exit) assert((this.a - x) != 0); return this.a - x; } } class B : A { this() { super(4); } } void main() { import std.stdio : writeln; auto a = new A(2); //writeln(a.foo(0)); //causes assertion failure //writeln(a.foo(2)); //causes assertion failure auto b = new B(); //writeln(b.foo(0)); //causes assertion failure //writeln(b.foo(4)); //causes assertion failure }
Mar 10 2016
On Thursday, 10 March 2016 at 23:31:14 UTC, jmh530 wrote:On Thursday, 10 March 2016 at 22:57:41 UTC, Jonathan M Davis wrote:You're not using in or out contracts here at all, so so of course, you're not going to see how in/out contracts work with inheritance in this example. To quote http://dlang.org/spec/contracts.html: ============ If a function in a derived class overrides a function in its super class, then only one of the in contracts of the function and its base functions must be satisfied. Overriding functions then becomes a process of loosening the in contracts. A function without an in contract means that any values of the function parameters are allowed. This implies that if any function in an inheritance hierarchy has no in contract, then in contracts on functions overriding it have no useful effect. Conversely, all of the out contracts need to be satisfied, so overriding functions becomes a processes of tightening the out contracts. ============ So, it's essentially assert(baseInContract || derivedInContract); and assert(baseOutContract && derivedOutContract); And here is a totally contrived example: ============ import core.exception; import std.exception; class Base { int foo(int i) in { assert(i > 0 && i < 10, "base in failed"); } out(result) { assert(result % 2 == 0, "base out failed"); } body { return i; } } class Derived : Base { override int foo(int i) in { assert(i < 50, "derived in failed"); } out(result) { // Combined with the Base.foo out contract, this ends up // being equivalent to assert(result % 6). assert(result % 3 == 0, "derived out failed"); } body { return i; } } void main() { Base base = new Base; Base derived = new Derived; assertNotThrown!AssertError(base.foo(4)); assertNotThrown!AssertError(base.foo(6)); assert(collectExceptionMsg!AssertError(base.foo(0)) == "base in failed"); assert(collectExceptionMsg!AssertError(base.foo(12)) == "base in failed"); assert(collectExceptionMsg!AssertError(base.foo(100)) == "base in failed"); assert(collectExceptionMsg!AssertError(base.foo(5)) == "base out failed"); assert(collectExceptionMsg!AssertError(base.foo(9)) == "base out failed"); // Combining the out contracts of the Base and Derived make this fail for // Derived when it succeeded for Base alone. assert(collectExceptionMsg!AssertError(derived.foo(4)) == "derived out failed"); assertNotThrown!AssertError(derived.foo(6)); // same as with Base assertNotThrown!AssertError(derived.foo(0)); // Derived allows <= 0 assertNotThrown!AssertError(derived.foo(12)); // Derived alows >= 10 && < 50 // Whether it's the Base or Derived that fails here is implementation defined, // since it fails for both. assert(collectExceptionMsg!AssertError(base.foo(100)) == "base in failed"); // This fails the contracts of both Base and Derived assert(collectExceptionMsg!AssertError(derived.foo(5)) == "base out failed"); // This passes Derived's contract, but it still doesn't pass Base's contract. assert(collectExceptionMsg!AssertError(derived.foo(9)) == "base out failed"); } ============ In order to get this same behavior without it being built into the language, all of the in contracts and out contracts from base classes would have to be repeated in the derived classes and be ||ed or &&ed appropriately. It's feasible, but it's error-prone and not particularly maintainable. And considering how easily this subject tends to confuse folks and how hard it can be to get it right - especially with more complicated contracts - I seriously question that it's going to be done right except rarely if the programmer doesn't have help like we do by having the contracts built into the language in D. - Jonathan M DavisIMHO, at this point, inheritance is the only reason they're worth having in the language. [snip]I created a simple example to understand your point about contracts only really mattering for inheritance, but my example is giving assertion errors for the inherited class the same way as the base class. What would I need to do for this issue to become apparent? class A { int a; this(int x) { a = x; } int foo(int x) { assert(x != 0); scope(exit) assert((this.a - x) != 0); return this.a - x; } } class B : A { this() { super(4); } } void main() { import std.stdio : writeln; auto a = new A(2); //writeln(a.foo(0)); //causes assertion failure //writeln(a.foo(2)); //causes assertion failure auto b = new B(); //writeln(b.foo(0)); //causes assertion failure //writeln(b.foo(4)); //causes assertion failure }
Mar 10 2016
On Friday, 11 March 2016 at 00:07:45 UTC, Jonathan M Davis wrote:You're not using in or out contracts here at all, so so of course, you're not going to see how in/out contracts work with inheritance in this example. To quote http://dlang.org/spec/contracts.html:Sigh...my point was that I was replacing the in/out contracts with what you were saying about asserts and scope(exit). Not that I have no idea what in/out contracts are. I will review what you wrote.
Mar 10 2016
On Friday, 11 March 2016 at 01:04:05 UTC, jmh530 wrote:On Friday, 11 March 2016 at 00:07:45 UTC, Jonathan M Davis wrote:Sure, but if you're not using in/out contracts with a class, you're not going to see how they interact with inheritance. To mimic what they do, you'd have to duplicate the base class contracts in the derived class and make sure that you ||ed the in contracts correctly and &&ed the out contracts correctly, which isn't very maintainable. - Jonathan M DavisYou're not using in or out contracts here at all, so so of course, you're not going to see how in/out contracts work with inheritance in this example. To quote http://dlang.org/spec/contracts.html:Sigh...my point was that I was replacing the in/out contracts with what you were saying about asserts and scope(exit). Not that I have no idea what in/out contracts are.
Mar 10 2016
On Friday, 11 March 2016 at 01:45:36 UTC, Jonathan M Davis wrote:Sure, but if you're not using in/out contracts with a class, you're not going to see how they interact with inheritance. To mimic what they do, you'd have to duplicate the base class contracts in the derived class and make sure that you ||ed the in contracts correctly and &&ed the out contracts correctly, which isn't very maintainable. - Jonathan M DavisIf I'm understanding you correctly, you could still get the same behavior with something like below (though it isn't exactly right since Base.foo's in block would technically have a funky rule applied to it). After playing around with your example, I'm finding in/out blocks on derived classes to be tricky. I think I'm going to try to avoid putting myself in a situation where I would screw something up. import core.exception; import std.exception; class Base { void baseCheck(int i) { assert(i % 2 == 0, "base out failed"); } int foo(int i) { assert(i > 0 && i < 10, "base in failed"); scope(exit) baseCheck(i); return i; } } class Derived : Base { override int foo(int i) { scope(exit) baseCheck(i); assert(i < 50, "derived in failed"); scope(exit) assert(i % 3 == 0, "derived out failed"); return i; } }
Mar 10 2016
On Friday, 11 March 2016 at 04:17:51 UTC, jmh530 wrote:On Friday, 11 March 2016 at 01:45:36 UTC, Jonathan M Davis wrote:Yeah, you can do something like that and get it to work, but it gets increasingly tricky, the more complicated the contracts are, and it's pretty easy to screw it up. Certainly, it's far cleaner and less error-prone to have it built into the language like we do.Sure, but if you're not using in/out contracts with a class, you're not going to see how they interact with inheritance. To mimic what they do, you'd have to duplicate the base class contracts in the derived class and make sure that you ||ed the in contracts correctly and &&ed the out contracts correctly, which isn't very maintainable. - Jonathan M DavisIf I'm understanding you correctly, you could still get the same behavior with something like below (though it isn't exactly right since Base.foo's in block would technically have a funky rule applied to it).After playing around with your example, I'm finding in/out blocks on derived classes to be tricky. I think I'm going to try to avoid putting myself in a situation where I would screw something up.Yeah. Having complicated contracts is probably a bad idea in general, but it gets far worse when inheritance is involved. And you can give yourself some weird problems even with the built-in help that D gives you. For instance, in my example, the derived class' in contract was a looser version of the base class' in contract, which is usually what you'd be looking to do, but the way it works is that the bass class' in contract and the derived class' in contract are ||ed. So, technically, you could make it so that the two contracts are completely distinct or so that the derived class' in contract is tighter, but what you ultimately end up with is the two in contracts ||ed, whereas what most folks will probably expect at a glance is that the derived class' contract will be met. So, doing something like base class: assert(i < 50); derived class: assert(i < 10); or bass class: assert(i < 50); derived class: assert(i > 50); will quickly make it so that you could have a derived class function which expects the derived class' in contract to be pass when it doesn't, because what's tested is the ||ing or the contracts not just the derived class contract. So, ultimately, you still have to be familiar with how the in and out contracts are supposed to work with inheritance to avoid shooting yourself in the foot, but having it built in makes it so that it's harder to screw it up. It just doesn't fix the whole problem for you. Regardless, I'd be _very_ careful with contracts and inheritance, and having complicated contracts with inheritance seems a bit suicidal. - Jonathan M Davis
Mar 10 2016
On Thursday, 10 March 2016 at 22:57:41 UTC, Jonathan M Davis wrote:I don't think that out contracts are worth much anyway, since in almost all cases, unit tests do that job already, and it's usually much easier to write test that specific input gives specific output than it is to have a general test at the end of the function.This is a very good point. I was already using unittests and I guess they make 'out' contracts redundant considering that you are ensuring the result of a function is correct anyway.If/Once we get more features in the compiler which take advantage of contracts, then I'll likely change my tune on that one, but for now, IMHO, they're really only of value in classes.I'm curious, what kind of features might come out of contracts in the future? They seem somewhat helpful in terms of QA and organization, but what kind of compiler or performance gains could you gain from this system? I'm going to take your advice and just stick to assertions at the beginning of the function and unittests to verify correctness of output. I'm already overjoyed with the easiness and integration of the unittest system as it is :) .
Mar 10 2016
On Thursday, 10 March 2016 at 23:31:22 UTC, FatalCatharsis wrote:On Thursday, 10 March 2016 at 22:57:41 UTC, Jonathan M Davis wrote:Well, if you had something like auto foo(int i) in { assert(i > 10 && i < short.max + 5); } body { ... } and the the code called it with a value that's known at compile time - e.g. foo(2) - and that value clearly fails the contract, then the compiler could treat that as an error. Similarly, it would know the valid range of i within the function body, which might help with optimizations or with VRP (value range propagation - i.e. how it can know that an integral value can fit in a smaller integral type and use an implicit cast rather than requiring an explicit cast). Or if you do something with out contracts, e.g. int foo(int i) out(result) { assert(result => 0 && result < 100); } body { ... } then in theory, the compiler could assume that the result passed the out contract and optimize based on that. For instance, with the code above, maybe it would then treat the result of foo as fitting it a ubyte without a cast, because it would know that as long as the contract passed, it would fit. e.g. ubyte bar = foo(9); Now, I suspect that stuff like that tends to be restricted to basic examples, particularly since you usually pass variables to functions, not literals, and at the moment, I can't think of much useful other than VRP for what could be done with out contracts, but in theory, the compiler would know more about what the function parameters and return value, and in at least some cases, it could optimize based on that or allow certain operations that might not be done implicitly in the general case. So, at this point, I think that it's pretty much all theoretical, but we might benefit from it at some point. And given that we don't know when such improvements might be made or exactly what code they'd benefit, it's arguably better to just use in and out contracts now just in case we get those improvements later, and the code then benefits without you having to change it, but personally, I don't think that that's worth the extra syntactic mess, particularly since while such improvements have been discussed upon occasion, it's not at all clear that we're ever going to get anything like them. - Jonathan M DavisIf/Once we get more features in the compiler which take advantage of contracts, then I'll likely change my tune on that one, but for now, IMHO, they're really only of value in classes.I'm curious, what kind of features might come out of contracts in the future? They seem somewhat helpful in terms of QA and organization, but what kind of compiler or performance gains could you gain from this system?
Mar 10 2016
On 2016-03-10 23:57, Jonathan M Davis wrote:IMHO, at this point, inheritance is the only reason they're worth having in the language. Without inheritance, in contracts could just as easily be assertions at the top of the function, and while out contracts are certainly easier as they are now rather than having to put a contract at each return statement or use a scope(exit) statement to do the out contracts, you can still do out contracts that way, and honestly, I don't think that out contracts are worth much anyway, since in almost all cases, unit tests do that job already, and it's usually much easier to write test that specific input gives specific output than it is to have a general test at the end of the function. But while in most cases, in and out contracts are trivially done in the function itself, inheritance is a whole other kettle of fish, and having them built into the language solves that problem whereas doing it yourself is actually fairly hard and error-prone. So, for that reason, and that reason alone, I think that having them built into the language is a good thing. There has been some discussion of getting the compiler to catch some stuff based on in contracts, in which case, in contracts would be worth a bit more, but as it stands, I pretty much never use out contracts (because I think that they're useless), and if inheritance isn't involved (which it usually isn't), then I don't even bother with an in contract and just put the assertions in the body and avoid the extra syntax. If/Once we get more features in the compiler which take advantage of contracts, then I'll likely change my tune on that one, but for now, IMHO, they're really only of value in classes.Another advantage of the contracts would be if the caller would be responsible for running the contracts. That is currently not the case. Then a library could be shipped with contracts and it's up to the user of the library to decide if the contracts should be executed or not. I don't want to start a discussion which approach is right and which is wrong. -- /Jacob Carlborg
Mar 11 2016
On Friday, 11 March 2016 at 10:22:06 UTC, Jacob Carlborg wrote:Another advantage of the contracts would be if the caller would be responsible for running the contracts. That is currently not the case. Then a library could be shipped with contracts and it's up to the user of the library to decide if the contracts should be executed or not.The biggest problem with associating the contract with the source rather than the caller is that you only get those assertions when that library was built with assertions enabled, whereas in contracts are really testing the caller code, so what you really want is for them to be enabled or not depending on whether the calling code is compiled with assertions enabled or not. So, having the in contracts are compiled in based on the calling code would be huge IMHO (and certainly make using explicit in contracts very valuable), but there are definitely implementation issues with pulling that off. Right now, in contracts are basically just the beginning part of the function, whereas they need to be separate from it for the caller to be able to determine whether they're enabled or not, and how to do that in a way that works with our calling conventions is not immediately obvious. Whether out contracts or invariants should be compiled in based on the caller is far less obvious though, since they're really testing the code that they're in, whereas in contracts are really testing the caller. Regardless, if in contracts were improved to be compiled in or not based on how the calling code is compiled would definitely make it so that I'd use in contracts over just putting the assertions at the top of the function. - Jonathan M Davis
Mar 11 2016