[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: contractual programming


I received a message from Julian asking me why I was neglecting the check-type
function.  I don't have my copy of the Dylan spec on my person right now, but
this could all be moot.

This is another long message, so if you're sick of this thread, junk it now.

/// OK, I can see that checking the object's contract after each modification
/// could well be the wrong thing.  There are places in the code where you want
/// to be sure that the contract is valid, and strecthes where it can't be.

This assumes that only classes will have contracts.  While class contracts are
an enormous help during development and maintenance (read: debugging), they are
not the entire focus of programming with contracts.  Assumptions for methods
about their parameters (like: X will not be NULL, or X will not be zero) can
be checked by hand, but this may disrupt the flow of the code if not given
a seperate 'slot' in the method.

Giving them a single spot allows them to function as documentation, although
they will never replace comments.  It can be extremely useful to know what
assumptions a method makes about its parameters.

/// Given this, I don't think it's obvious that method exit is always the
/// right place to check.  One might call several sub-methods that mess up an
/// object before some surrounding method fixes it again.

The issues are more complex than all this, yes.  Contracts for classes are
difficult to implement correctly without losing the elegance of the idea.
I might suggest checking method parameters when it is entered according to the
contracts of the classes specified for that method, and again for the return
value.  (The return value is a special case, though, and really deserves to
be treated separately -- as a contract of its own)

/// All of which brings me back to the view that the user should just insert
/// ASSERT or CHECK-CONTRACT calls where he thinks they belong.

Is this because of the difficulty in catching _every_ violation of a contract?
It's nice to have the guarantee that every violation will be reported (at
least for debugging -- so one knows the true root of the semantic error).

Perhaps a good compromise would be to turn contract checking off in a block
of code?  Like many LISP nuts, I'm not a big fan of set!/setq, and think that
object interfaces should be as impervious as possible to the deliterious
effects of modifying existing structures, ...but set! has its place, as does
setter in escaping the limitations of functional programming.  Perhaps a
'mute' or 'alter' form would be in order?

	(mute my-object
		slot1: new-slot1
		slot2: new-slot2)

Given this, I could be quite satisfied if contract checking were only done
after calls to make, mute, or when explicitly requested by the programmer.
This would only leave the [god-awful] task of figuring out how to conditionally
compile this code.  It might even (in a static program) be possible to pay
for contract checking only when one uses it.

This approach as the advantage of specifying where a contract check needs to
be done, and also may (I haven't thought about this too extensively) serve to
document code.  It would also encourage use of (setter slot-n), since it should
use it/them.

/// -- Scott

	-- Scot


BTW, here are the three types of contracts that I consider crucial to a
    contractual programming style.

	1) As a check on the return value of a method or a function.

		(define-method sqrt ((x <something>))
			range-contract: (curry >= 0)
			;; range contracts as methods on the [un-named]
			;; return value
			;; this one has caught more bugs for me than any
			;; other...

	2) As a check on the parameters to a method or a function.

		(define-method sqrt ((x <something>))
			domain-contract: (>= x 0)
			;; domain contracts as s-expressions in the context
			;; of the method
			;; this one catches about as many bugs as a class
			;; contract, in _my_ code, at least...

	3) As a check of the validity of a class.  Note that verifying the
	   class must also include it's superclasses and subclasses.

Of course, it must also be possible to disable these checks without altering
the source code -- rather like C's #ifdef if restricted to DEBUG.