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

Reply via email to