Contracts for delegates

by Colin Adams (modified: 2007 Oct 17)

Routines that simply forward the request to another object (a delegate) typically have the same signature and contract (possibly strengthened) as the delegate's routine. In which case it is slightly tedious to write out the duplicate assertions (especially if the delegating routine is using different names for the arguments), and a potential maintenance problem too.

It appears that there is a simple, problem-free, extension to the assertion syntax to improve this situation - reusing the like keyword within assertions.

The suggested syntax looks like this:

my_feature (...signature...)... -- Delegating feature require my_precondition_tag: like my_object.feature_name additional_preconditions: ... do ... other code ... my_object.feature_name (...arguments-to-`my_feature'...) ... other_code ... ensure my_postcondition_tag: like my_object.feature_name end

It is an error if `feature_name' does not have the same (identical?, conforming?) signature as `my_object.feature_name'.

The preconditions for `my_feature' are now the same as those of `my_object.feature_name' (with appropriate substitution of argument names) plus the additional preconditions declared in `my_feature'.

Similarly for the postconditions.

  • Martin Seiler (16 years ago 18/10/2007)

    may be problematic

    what about contracts involving queries and not just the feature arguments? just ignore them?

    -- mTn-_-|

    • Colin Adams (16 years ago 18/10/2007)

      Query renaming must be dealt with also

      Yes, it's an interesting point. Assuming the delegate is class A, and the delegating class is class B, and both classes A and B inherit from class C, and class A uses a query from class C in one of the preconditions, and class B renames this query, then the expansion of the like precondition will involve using B's name for the query.

      That isn't actually a problem. For Manu's suggestion of a tool solution, it means the tool has to be ever-so-slightly more sophisticated.

      For my language enhancement suggestion, it's a no-op (that is, it's just part of the semantics). Colin Adams

  • Manu (16 years ago 18/10/2007)

    Good idea

    We certainly need something like that, but I wonder whether it should be a language change or a tool change. I'm favoring the tool because when you have the call a.f' and you want to extract the assertions, then in your model you need to make sure that a' is also available to the client which is not always the case. With a tool, then the tool can basically expand your `like a.f' notation in usable assertions.

    • Colin Adams (16 years ago 18/10/2007)

      Not maintenance proof

      Yes, that is helpful, but it doesn't help if the author of the delegate class realizes (s)he made a mistake, and strenghtens the precondition or weakens the postcondition. Of course, this is a more general problem, and my proposal only helps for a small subset of such cases.

      Colin Adams

  • Paul Bates (16 years ago 19/10/2007)

    I'd like it!

    I had a similar idea for a Eiffel-based language compiler I was working on for Lego MINDSTORMS NXT.

    In your example there's a couple of issues. First you are using a variable/argument to access the conditions. What if the variable is unattached? Are you using the dynamic type or the static type? You cannot use the dynamic type because the client cannot verify the contracts as well as the possibility of 'my_object' resting in an detached form. Usage in a form of a static access call instead ensures the the routine 'my_feature' is borrowing the contract from is assured and clear.

    ... require my_precondition_tag: like {MY_OBJECT}.feature_name ..

    I like the reuse of like. And I'd never thought about trying to reuse contracts from a feature with a differing signature. However, there are problems in borrowing contracts from features of differing signatures:

    my_feature (a: STRING_32; b: INTEGER) require my_precondition_tag: like feature_name local res: like feature_name do is_initialized := True res := feature_name (a, 10, create {MY_TEST}) end feature_name (a: INTEGER; b: INTEGER; c: MY_TEST): BOOLEAN require a_is_positive: a > 0 a_is_less_than_b: a < b c_attached: c /= Void b_is_in_range: c.is_valid_range (b) is_initialized: is_initialized do ... end

    The first issue is that 'feature_name' has an additional parameter 'c', which must be attached and is used to validate the argument 'b'. How can we borrow the contracts for 'feature_name' when there is no specification in 'my_feature' for it?

    Also, in the above example 'feature_name' requires 'is_initialized' to be True but 'my_feature' does not. How do you selectively exclude contracts?

    You might use a tuple to select the contracts you want to borrow:

    my_feature (a: STRING_32; b: INTEGER) require my_precondition_tag: like feature_name [ a_is_positive (b), a_is_less_than_b (b, 10) ] end local res: like feature_name do is_initialized := True res := feature_name (a, 10, create {MY_TEST}) end

    But with this we are already becoming more verbose, less readable and are not saving a whole lot on typing.

    There's also the problem that borrowing contracts from 'feature_name' might break clients of 'my_feature', if contracts are added or removed from 'feature_name'. Unless you keep a vigilant eye on it the contracts of 'feature_name', 'my_feature' may be broken unnecessarily or allow invalid state to pass through. This a maintenance nightmare!

    I really like the idea but it requires a lot of thought.

    • Colin Adams (16 years ago 19/10/2007)

      Unattached variables not a problem

      It doesn't matter if the variable is attached or not (for the contract - the body will require a suitable invariant, but that has nothing to do with the contract) - it is the static type of the variable that is used.

      I hadn't thought about borrowing contracts with a different signature. That is a minefield that I am not eager to explore.

      As for contracts being added or removed from the delegated feature, the whole point of making it a language enhancement is that the delegating class remains correct.

      As for the clients of the delegating class, they are no worse off than if the class itself is edited.

      Colin Adams

      • Paul Bates (16 years ago 27/10/2007)

        Just coming back to add another couple of cents to this; I would say that using a variable isn't the best idea and using static access is better. It's clear where the feature invariants are taken from, it resolves using attributes/routine parameters using anchored types (This could make assertions differ depending on descending classes), and finally solves any issues when a class attribute/routine parameter is not available. You could argue to provide both ways, but Eiffel's principles prohibit multifaceted mechanisms unlike our friend Perl :)