[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