[E-Lang] Syntax change: reducing side-effects

Mark S. Miller markm@caplet.com
Mon, 12 Feb 2001 01:00:36 -0800

At 07:39 PM Sunday 2/11/01, Dan Moniz wrote:
>I fully support anything that'll make my work easier, of course, and this,
>along with the new twine data type are welcome changes.

This change makes good compilation much easier.  A win all around.

>Something I was thinking of the other day -- Eiffel-like Design by Contract
>in E. Doable? 

I'll answer this first while pretending not to have read yet the rest of 
your message, so I can start with the conventional interpretation of "Design 
by Contract".

Yes and no, sorta kinda.  There are several aspects to Eiffel's support for 
design by contract.  In a way, all it is is a fancy assertion management and 
checking system for message pre-conditions & post-conditions, and 
object-state invariants.  In any language with the equivalent of C's assert, 
you can simply assert pre-conditions at the beginning of your methods, 
assert post-conditions at the end of your methods, and assert object-state 
invariants at places after you have mutated state in the flow control 
where the object is supposed to be consistent again, such as at the end of 
public mutating methods.

There are many linguistic features that Eiffel adds to this, but to my mind 
the most valuable addition is a notation (a conventional place to put the 
checks), set of tools (which rewards your efforts), and a culture of 
expectations (seeded by Meyer's many books, some quite good), that nudge the 
programmer into actually putting such checks into their programs, and to 
attempting to design abstractions that can be adequately checked by such 
tools.  This design for checkability may be more valuable than the actual 

Looked at in this way, E isn't as good as Eiffel, but it ain't too shabby.  
By generalizing type declarations to guard expressions, E allows arbitrary 
user-defined correctness checks (and more) to be placed on parameter 
variables (for variable-based pre-conditions), return values (for 
post-conditions), instance variables (for variable-based invariants), and 
on any other variables you have laying around.  Dean reminded me that, unlike 
Eiffel, I need the "variable-based" qualifier above because my guards 
naturally only constrain each variable separately, rather than constraining 
the incoming message or object state as a whole.

                     require() and the "_{ expr }" thunk syntax

E does have an assert-like construct named "require".  In at least 
stl-0.8.9k on, it's used like this:

    require(x*y < 37, _{ `Product of $x and $y must be smaller` })

and implemented as

    define require0 {
        to (cond, thunk) {
            if (! cond) { throw(thunk()) }
        to (cond) {
            require0(cond, _{"required condition failed"})

Recall that "_{ expr }" expands to "define _() :any { expr }".

(Don't worry about "require" vs "require0".  It's a kludge to get around a 
name-space bootstrapping issue.)

If the "cond" argument to require is false, it calls its thunk argument and 
throws what the thunk returns.  (The other clause just provides a default 
problem report to throw.)  This gives me an opportunity to follow up on an 
earlier thread I let drop:  Despite our earlier thread about dangerous 
accidental return-value leakage, I defined "_{ expr }" to expand using 
":any" rather than ":void" because I expect this syntax to be most often 
used for calculate-on-demand expressions, as above.  If the cond argument is 
true, the thunk argument never gets called.

Ignoring the thunking of the second argument, this seems an awful lot like 
an assert.  However, it has one crucial difference which takes E farther 
from Eiffel.  An assert is an optional check.  A require is a mandatory one. 
Our guards also differ from Eiffel's checks in this way -- they also 
express mandatory check.  These checks are part of the semantics of the E 
program.  If you removed them from a correct E program, or somehow silently 
turned them off, you would have a broken program.  The removal is not 
correctness preserving.

For invariants and post-conditions, this is probably a weakness of E 
compared to Eiffel.  For Eiffel's design goals, this inability to turn off 
E's pre-condition checking would also be seen as a weakness.  However, for 
E's design goals, an object with *optional* preconditions checking is 
probably a security hole.  With such checking off, it "relies" on its 
clients, and so is "delicate".  While on rare occasions it is appropriate to 
code a delicate object, capability style is to avoid these like the plague.  
An object's correctness should almost never be at the mercy of the behavior 
of its clients.  The occasional delicate object should be painted in 
blinking red neon polka dots, so no one forgets to handle it with care.

While E could probably be improved by importing more of Eiffel's virtues, 
I'm more interested in annotations like the old "rely" and "suspect", that 
are more directly targeted at the issues capability programmers need to 
worry about.  See also Marc Stiegler's rather amazing 
http://www.skyhunter.com/marcs/trustrule.htm .  We know it to have many 
fatal flaws, but it points the way to something very important.

                            Contract vs Contract

>If so, here's a completely not-thought-out extrapolation:
>Smart Contracts in E that contract other E installations to build E
>programs. Self-generating mobile code (of a sort) designed by contract
>(complete with confinement, etc.).
>I have to run the idea through more particular paces, and see if there are
>any real places it would be valuable, but it seems, at first glance, to be

My first reaction to this was:  Now wait a second.  What the 
Object/Eiffel/"Design by Contract" people mean by "Contract" has nothing to 
do with what Smart Contracting people mean by "Contract".  But then I 
realized that they both have strong similarities with real world contracts, 
from which both stole the term.  While "similar to" isn't necessarily 
transitive, it certainly make a connection plausible.

However, having realized a connection is possible, I still don't see one.

In any case, if I understand you, the harder issue above is the automatic 
generation of an implementation from a specification.  Although impressive 
work on this has been done (back when people did that sort of thing), it's 
so much harder than everything else we're doing put together that it's not 
high on my agenda.  That fruit hangs so high you need a spaceship to pick it.