[e-lang] Proposal: Scopes, Maps, and Slots, oh my

Miller, Mark e-lang@mail.eros-os.org
Wed, 19 Nov 2003 15:54:54 -0800

> 2) De-emphasizing Scope objects, substituting in their stead, in many 
>    contexts, maps representing the same name-to-value bindings. In the 
>    safeScope, "safeScope" would be bound to a ConstMap representing the 
>    bindings of the safeScope. This seems better.
> Given that we go in direction #2, there remains the question of how we
> represent a Scope's bindings in a map. As answering this relates to 
> several other issues -- the proposed new guard semantics, an upcoming 
> module system proposal, an improved way of stating the semantics of 
> Kernel-E -- I'll expand on this in another thread.

This is that thread.

Let's start with our old example

    def makePoint(x :int, y :int) :near {
        ^def point {
            to getX() :int { ^x }
            to getY() :int { ^y }
    }   }

    ? def pt := makePoint(3, 5)

and ask, what's the environment with which "def point { ... }" is evaluated?
We need only include bindings for variable names used freely within point,
which are "x", "y", and "int".

The semantics of lexically scoped, lambda-based languages like Scheme 
features a mapping called an Environment, or "Env". To describe the lambda
calculus itself, or purely functional lambda languages, Env is a mapping
from names (variable names) to values (the values of these variables in
that execution context). For these, point's Env is: 
    ["x" => 3, "y" => 5, "int" => int]
where the unquoted int is, let's say, the value bound to "int" in the 
safeScope. (For now, the bindings in the safeScope are unshadowable, so we
can postpone untangling this issues that would be raised here.)

E and Scheme introduce side-effects in similar ways, by considering the Env
to be a mapping from names to mutable objects holding the current values
of their respective variables. In Scheme, these "location" objects are
not reified, but are purely explanatory. All variables are explained by the
same kind of location object, and all variables are assignable. For Scheme,
one might say that point's Env is:
    def intLoc := makeLocation(int) # sometime earlier
    def xLoc := makeLocation(3)
    def yLoc := makeLocation(5)
    ["x" => xLoc, "y" => yLoc, "int" => intLoc]
where each call to makeLocation makes a new unique location.

In E, locations are called "Slots" and are fully reified. Where the 
expression "x" evaluates to the current value of the variable named "x", the 
expression "&x" evaluates to the slot object which provides this current
value. The expression "&x" is, therefore, more fundamental, since "x" is 
equivalent to "(&x).getValue()".

As patterns, "&x", "x", and "var x" (considered without guards for now) are 
all defining occurrences of the variable named "x", bringing the variable 
named "x" into scope by adding a binding to the current environment. When 
the pattern is "&x", then the specimen itself becomes the slot associated 
with this variable. When the pattern is "x" or "var x", then the specimen
becomes the initial value of the variable according to the created slot.
There are two built in slot creators, let's say "__makeFinalSlot" and 
"__makeVarSlot". The pattern "&x" is almost more fundamental than the other
two since the pattern "x" is equivalent to 
"temp ? (__makeFinalSlot(temp) =~ &x; true)",
and the pattern "var x" is equivalent to
"temp ? (__makeVarSlot(temp) =~ &x; true)". (I say "almost" above because
the use of "temp" in these expansions is itself the kind of final pattern
we're trying to explain by other means.)

__makeFinalSlot creates a FinalSlot, which is PassByCopy, and so two calls 
to __makeFinalSlot with the same argument yield equivalent values. So, for 
E, we would describe point's Env as:

    ["&x" => __makeFinalSlot(3), "&y" => __makeFinalSlot(5),
     "&int" => __makeFinalSlot(int)]

This representation explicitly represents the semantics of the environment.
We have the keys in this representation all begin with "&" in order to
enable a shorthand for the typical case. In the E implementation, wherever
a map is used to represent an environment, if the key isn't a "&" followed
by an E identifier, then it must be an E identifier, and the value is taken
to be the value of the corresponding final variable. In other words, we
may write the above in the shorthand:

    ["x" => 3, "y" => 5, "int" => int]

As a map, this is a different object. But as a map used to describe an
environment, it describes the same environment.

When an assignment expression, for example "x := val", is statically 
accepted, then it is equivalent to 
"((&x).setValue(def temp := val); temp)". An assignment to "x" is only 
accepted if "x" is defined by a "&x" or "var x " pattern.

The __makeFinalSlot and __makeVarSlot used above need to be primitive to 
avoid infinite regress, but, ignoring that, they are equivalent to the 

interface Slot {
    to getValue() :any
    to setValue(newValue) :void
    to isFinal() :boolean

def __makeFinalSlot(value) :Slot {
    def finalSlot implements Slot, PassByCopy {
        to getValue() :any { ^value }
        to setValue(newValue) :void {
            throw("Can't assign to final variable")
        to isFinal() :boolean { ^true }
}   }

def __makeVarSlot(var value) :Slot {
    def varSlot implements Slot {
        to getValue() :any { ^value }
        to setValue(newValue) :void { value := newValue }
        to isFinal() :boolean { ^false }
}   }

More soon, I hope...

Text by me above is hereby placed in the public domain