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

Dean Tribble tribble@e-dean.com
Sun, 11 Feb 2001 14:38:47 -0800


In the current E language, any name can be assigned unless it is specified 
as :final.  As is the case with most good programming, however, most names 
only ever get one value at initialization time, and programmers typically 
assume that they always have that value.  For example, when you see 'define 
MintMaker(name) :any {' in the MintMaker program, you ought to be able to 
assume that the value of the 'MintMaker' binding won't change over 
time!  But in the current language, it could perfectly well be assigned 
somewhere later in the module, and then even references to it internal to 
its code would refer unknowingly to the new MintMaker.  A similar case can 
be made for most arguments, pattern variables, and even local 
variables.  Assignment is the fundamental source of race conditions and 
other non-local dependencies in software systems.

To address this problem, I have the following proposal (with help from 
MarkM and Marcs):

We should change the syntax (in as painless a way as possible) so it is 
statically apparent at name definition time whether a variable could be 
assigned.

Corollary: Assignments can only be made to names that are statically 
declared as assignable.  All other names are pure, immutable bindings.

We went through various unacceptable alternatives which I will summarize if 
the discussion warrants it.  Here I will just propose the one that actually 
appears to just work, and has various other unifying effects on the syntax.

Summary: all defining occurrences of names now default to 'final', unless 
they are prefixed by 'var', in which case they are settable.  In addition, 
'var' can be used as a top-level construct as an alternative to 'define' 
(equivalent to 'define var').

In detail:

- This change applies to all defining occurrences of bindings ('define', 
patterns, argument to methods, etc.)

- Reserve the new keyword, 'var'.

- For defining occurrence without the 'var' keyword, the guard is always 
and only a ValueGuard.  If absent, the guard defaults to the ValueGuard, 
':any'.  It can be translated into Kernel E as always being wrapped with a 
'final' SlotMaker.  The compiler must statically reject assignments to 
names defined without 'var'.

- For defining occurrences that are prefixed with the 'var' keyword, the 
guard is always a SlotGuard.  If absent, it defaults to ':settable'.

- User-defined SlotGuards can only be used with a 'var' prefix.  As a 
result, the reader of the code (including a compiler) is guaranteed that 
unless there is a 'var' prefix, the value of the variable will only ever be 
the initial value (i.e., user-defined SlotGuards can allow assignments).

- 'var' as the top-level keyword for an expression is equivalent to 'define 
var'.

Below are a few examples.  The only change to MintMaker is in the argument 
to makePurse which declares the 'balance' instance variable.

define MintMaker(name) :any {
     define [sealer, unsealer] := BrandMaker pair(name)
     define mint {
         to printOn(out) { out print(`<$name's mint>`) }
         to makePurse(var balance : (any >= 0)) :any {
             define decr(amount : (0..balance)) {
                 balance -= amount
             }
             define purse {
                 to printOn(out)    { out print(`<has $balance $name bucks>`) }
                 to getBalance :any { balance }
                 to sprout     :any { mint makePurse(0) }
                 to getDecr    :any { sealer seal(decr) }
                 to deposit(amount : integer, src) {
                     unsealer unseal(src getDecr)(amount)
                     balance += amount
}   }   }   }   }

(Note that *I* collapsed the braces to one line :-).

The result, however, is that the programmer can know that all the other 
names (like 'purse', 'mint', 'sealer', etc.) are *in fact* defined once and 
never change, just like they automatically assume, albeit sometimes 
painfully incorrectly.  Just for the heck of it, I counted the number of 
changes required in the code in "E in a Walnut" for this change, and there 
were about 10 'var' declarations required.  Most were as in the example 
below where they declare mutable instance variables.  The others were in 
loops that accumulate things.

def carMaker new(name) :any {
     var xLocation := 0
     var yLocation := 0
     def car {
         to moveTo(x,y) {
             xLocation := x
             yLocation := y
         }
         to getX :any {xLocation}
         to getY :any {yLocation}
         to getName :any {name}
     }
}

The result, however, was that the 100+ other uses of 'def' in the document 
correctly supported the programmers assumption of immutability!

The other big consequence of this is that interpreter and compiler can use 
that information for substantial performance improvement for the by-far 
typical case of no assignment.

Comments?
dean