eiffelroom

blogContracts for delegates

colin-adams's picture

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.

mtn's picture

may be problematic

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

-- mTn-_-|

colin-adams's picture

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

manus_eiffel's picture

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's picture

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

paulbates's picture

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's picture

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

paulbates's picture

Just coming back to add

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 :)

about - contact