Boost logo

Boost :

Subject: Re: [boost] [contract] diff n1962
From: vicente.botet (vicente.botet_at_[hidden])
Date: 2010-04-09 02:00:22


----- Original Message -----
From: "Lorenzo Caminiti" <lorcaminiti_at_[hidden]>
To: <boost_at_[hidden]>
Sent: Friday, April 09, 2010 1:26 AM
Subject: [boost] [contract] diff n1962

>
> Hello all,

Hi Lorenzo,
 
> Below is a summary of the differences between Boost.Contract and the
> Contract Programming C++ standard proposal n1962. I know some of the
> authors of n1962, and also of its previous revisions, are on this
> mailing list -- I hope that they can provide comments at least.
>
> [n1962] Crowl and T. Ottosen, Proposal to add Contract Programming to
> C++ (revision 4), The C++ Standards Committee, 2006. All previous
> revisions of this proposal are also relevant (and related authors
> comments encouraged).
>
> Boost.Contract design was vastly based on n1962. However, in few
> places I deliberately made the different design decisions listed
> below. Based on your feedback, I would be happy to revisit my design
> decisions to align even more Boost.Contract with n1962.

Excelent idea. This comparation is very useful.
 
> 1) SUBCONTRACTING
> n1962 allows only the base class to specify preconditions. Instead,
> Boost.Contract allows also derived classes to specify preconditions.
>
> Boost.Contract fully supports subcontracting as defined by
> substitution principles, implemented by Eiffel, and specified by
> prevision revisions of n1962 (n1613). However, Boost.Contract could be
> modified to comply with n1962.
>
> Why does n1962 allow preconditions only for the base classes?
>
> n1962 section 4.2.1: <<If the function F is virtual, we require that
> 1. only the base virtual function of F can have a precondition.
> Section 3.5 of n1613 explains how little redefinition of preconditions
> is used. Even though subcontracting is theoretically sound, it ends up
> being fairly useless in practice [3].
> [3] A weaker precondition can be taken advantage of if we know the
> particular type of the object. If weaker preconditions should be
> allowed, then there exists two alternatives: to allow reuse of an
> existing contract or to require a complete redefinition. The former
> favours expressiveness, the latter favours overview.>>.

The contract of the overrriding function must be weaker than the overrided one. How can this been ensured otherwise?
 
>
>
> 3) ARBITRARY CODE IN CONTRACTS
> n1962 allows only a list of assertions, eventually guarded by
> if-statements, in invariants, preconditions, and postconditions.
> Instead, Boost.Contract allows for any C++ code in the contract.
>
> Boost.Contract documentation recommends to keep the contract code
> simple, limited to a list of assertions eventually guarded by
> if-statements (otherwise, it is more likely you have bugs in the
> contracts than in the actual code). However, I think it would be
> strange for a library to enforce such a constraint at compile-time --
> I agree that language support for CP should instead enforce this
> constraint at compile-time.
>
> Boost.Contract could be modified to enforce this constraint by
> removing the CONTRACT_ASSERT() macros. Would this complicate the macro
> syntax even more?
>
> template<typename T>
> class myvector {
>
> CONTRACT_FUNCTION_DECL(
> (iterator) (erase)( (iterator)(first) (iterator)(last) ) (copyable)
> (postcondition) (result) ( // no `{` bracket
> ( size() == (CONTRACT_OLDOF(this)->size() -
> std::distance(first, last) )
> ( if (empty()) (result == end()) ) // if-guarded condition
> ) // no `}` braket
> ...
> )
>
> ...
> };
>
> Note how the postconditions are just a preprocessor sequence of
> boolean expressions asserting the contract. The if-guarded conditions
> can AND (&&) multiple assertions plus they can list an optional 3rd
> element for the else block, for example:
>
> (precondition) (
> (if (check)
> ( then1 && then2 )
> else
> ( else1 && else 2 )
> ) // endif
> )
>
> If check is true then then1 and then2 conditions are asserted, else
> else1 and else2 conditions are asserted.
 
I don't find

 ( size() == (OLDOF(this)->size() - std::distance(first, last) )
 ( if (empty()) (result == end()) )
 
more complicated than

 ASSERT( size() == (OLDOF(this)->size() - std::distance(first, last) );
 if (empty()) ASSERT(result == end());

Even if the last one can be clearest as more explicit and use the usual sentence.

> 4) BODY KEYWORD
> n1962 does not use the body keyword. Instead, Boost.Contract macros use (body).
>
> Boost.Contract could be changed to remove the need for (body) -- the
> body will simply be the last element in the contract sequence.
>
> This way, and removing also the CONTRACT_ASSERT(), Boost.Contract
> syntax will be _exactly_ the same as n1962 with the "only" addition of
> the () preprocessor wrappings:
>
> template<typename T> // n1962 syntax
> class myvector {
>
> void push_back(const T& element)
> precondition {
> size() < max_size();
> }
> postcondition {
> back() == element;
> size() == (oldof size() + 1);
> }
> {
> vector_.push_back(element);
> }
>
> ...
> };
>
> template<typename T> // Boost.Contract syntax
> class myvector {
>
> CONTRACT_FUNCTION_DECL(
> (void) (push_back)( (const T&)(element) ) (copyable)
> (precondition) ( // no CONTRACT_ASSERT()
> ( size() < max_size() )
> )
> (postcondition) (
> ( back() == element )
> ( size() == (CONTRACT_OLDOF(this)->size() + 1) )
> )
> ({ // no (body)
> vector_.push_back(element);
> }) )
>
> ...
> };
>
> Is the macro syntax more readable with or without (body) and/or
> CONTRACT_ASSERT()?

I find it closer to the C++ syntax in N1962 and find a big improvement respect to the previous proposal.
 
>
>
> 7) NESTED FUNCTION CALLS (policy)
> n1962 disable nothing. Instead, Boost.Contract disables invariant
> checking within nested function calls.
>
> I found this policy useful to prevent infinite recursion. Previous
> revisions of n1962 (n1613) specified the Boost.Contract policy. Eiffel
> disables all checks (not just invariants). Boost.Contract could be
> changed to comply with n1962.
>
> Why does n1962 not disable invariants checking in nested calls?

Is there any reason to to disable them. COuld you elaborate which invariants checking Boost.Contract is disabling?
 
> 8) CODE ORDERING
> Both n1962 and Boost.Contract require preconditions > postconditions >
> body to be specified in this order. Instead, Eiffel uses preconditions
>> body > postconditions.
>
> People new to CP usually ask why the order is not pre > body > post
> when looking at the contracts for the 1st time. However, after using
> the library, the same people appreciate pre > post > body more because
> the programming is cleaner "first program the contracts (pre and post)
> then the body". I think using only one ordering is better to enforce
> code consistency and I prefer pre > post > body over Eiffel's pre >
> body > post.
>
> Do you think Boost.Contract should be modified to support both n1962
> and Eiffel orderings?

No. I think you need to provide just one ordering, and why not the N1962 ordering.
 
>
> 11) OLDOF
> n1962 allows to apply oldof to *any expression* that can be copied.
> Instead, Boost.Contract only supports CONTRACT_OLDOF() for *object and
> the function parameters* that can be copied.
>
> This is a limitation of Boost.Contract because, for example, in order
> to get the old value of a vector size I need to copy the entire vector
> adding more overhead respect to copying just the vector size (which is
> simply an integer):
>
> oldof vector.size() // n1962: Just copy the integer size().
> CONTRACT_OLDOF(vector).size() // Boost.Contract: Copy entire vector...
>
> Furthermore, if the vector object cannot be copied, I cannot not get
> its old size even if the size itself can be copied because it is just
> an integer:
>
> oldof noncopyable_vector.size() // n1962: OK, just copy the integer size().
> CONTRACT_OLDOF(noncopyable_vector).size() // Boost.Contract:
> Cannot do this because cannot copy noncopyable_vector...
>
> Finally, the use of CONTRACT_OLDOF(variable) requires programmers to
> explicitly indicate that the variable type is copyable using
> (copyable) in the function signature adding syntactic overhead.
>
> Unfortunately, I do not know how to remove these library limitations.
> (All of that said, Boost.Contract old value support is very useful and
> it successfully covered all the uses cases and examples presented in
> n1962 and in a number of C++/Eiffel books/articles.)

I don't know if the following could be considered as an optimization approach.
The contract function declaration could add a list of variables storing the old values of the required espressions.
For example if the postcondition will check for size() == (oldof(size()) + 1), we can store in a variable old_size the size before calling.

        CONTRACT_FUNCTION_DECL(
        (void) (push_back)( (const T&)(element) ) ((old_size) (size()))
            (precondition) (
                ( size() < max_size() )
            )
            (postcondition)
                ( back() == element )
                ( size() == (old_size + 1) )
            )
        ({
            vector_.push_back(element);
        }) )
 
 
Best,
Vicente


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk