Suggested Change to Contract Syntax
Jonathan M Davis via Digitalmars-d
digitalmars-d at puremagic.com
Thu Mar 10 16:07:45 PST 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:
>> 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
> }
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 Davis
More information about the Digitalmars-d
mailing list