A New Revealation: Semi-Permeable Membranes Mark S. Miller (markm@caplet.com)
Mon, 25 Oct 1999 19:38:20 -0700

(Per, I'm including you on the to: list for reasons that will be obvious.)

What do you get when you cross Chip's "^" syntax with Bill & Norm's memories of Algol68? A more expressive way to say whether a value is returned.

To start with the punch line, here's the old getterSetterPair() example http://www.erights.org/elib/capability/ode/ode-objects.html#facets rewritten using the syntax I'm proposing:

     define getterSetterPair(value) ^any {
         define getter()         ^any  { value }
         define setter(newValue) ^void { value := newValue }
         [getter, setter]

}

I had always intended to allow optional "type" declarations of function/method return values, just as I allow optional declaration "type" declarations on the defining occurrences of variables, including parameter variables. In both cases, since E has no static type system, "type" is not quite the right concept. I call these guards for reasons I explain later in this email.

Variable guards default to "any", meaning that any value, including deferred references and null, are allowed. Historically, return guards were implicitly "any" as well, and this whole discussion is effectively one of changing that default. In light of our discussion so far, it seemed reasonable to change the default return guard to be "void", and functions/methods that want to return a value would have to say "^any" or "^<some-more-specific-guard>". However, it rewriting some code to try out this convention, I realized that it is comparatively rare not to return something, so it's not worth having a default. I propose instead to always require a return guard. Before we explain what these guards actually are, here's a variant of the above specifically for integer values:

     define getterSetterPair(value : integer) ^any {
         define getter()                   ^integer { value }
         define setter(newValue : integer) ^void    { value := newValue }
         [getter, setter]

}

It works to read "^" as either "reveal" or "return". "getter reveals an integer." "setter accepts an integer and reveals nothing." Since it's now just how we say things in our head, we can be sloppy about whether we sub-vocalize "reveal" or "reveals".

Ok, what are these guards? How does the E kernel language use the guards? Can new guards be defined in E?

Yes, new guards can be defined in E. To avoid infinite regress, I've defined the core set of guards in Java, but I'll explain them here as if I implemented them in E.

Kernel E implicitly sends one of two messages to a guard, depending on which of two contexts the guard finds itself in. The two contexts are

  1. Guarding a return value

The guard is known as a ValueGuard. A ValueGuard is sent the message "coerce(specimen)" and it either return a coercing of the specimen it finds acceptable, or it throws an exception. Were "any" just a ValueGuard, its definition would be

     define any {
         to coerce(specimen) ^any { specimen }

}

This example also shows the infinite regress problem that prevented me from actually defining "any" in E.

Likewise, "void" would be defined as

     define void {
         to coerce(specimen) ^any { null }

}

Since it accepts all values, but coerces them all to null.

2) Guarding a variable definition.

The guard is known as a SlotGuard. A SlotGuard is sent the message "makeSlot(specimen)", and it is expected to return a Slot whose initial value is that specimen, or a coercion of that specimen.

A Slot is an object that responds to getValue(), and optionally to setValue(newValue). Since "any" is both a SlotGuard and a ValueGuard, its definition is equivalent to

     define any {
         to coerce(specimen) ^any { specimen }
         to makeSlot(value) ^any {
             define Slot {
                 to getValue           ^any  { value }
                 to setValue(newValue) ^void { value := newValue }
             }
         }

}

When you

define x : any := 3

the object implementing the variable "x" is equivalent to the Slot revealed by the above code. Likewise if you leave off the ": any", since it defaults to ": any". When x is used in an expression for its value, Kernel-E implicitly sets "getValue()" to this Slot. When "x" is used on the left side of an assignment, Kernel-E implicitly sends "setValue(rValue)" to this Slot. Finally, "&x" returns the Slot object representing the variable "x", as opposed to the value of the variable. Therefore the following two are equivalent:

x := x + 1

(&x) setValue((&x) getValue + 1)

Of course, for the normal cases a good compiler is expected to compile away all the extra implied overhead. Since some optimizing Scheme compilers (eg, Orbit) transform some "locations" (the scheme semantics term corresponding to our Slots) into reified objects in an intermediate stage of transformation, only to make it collapse out later, we may be in luck if we use compiler technology built for Scheme. Per?

To show the same pattern for "integer" let's pretend we had a separate primitive function that provides us with just the integer coercion and type check:

define integerCoerce(specimen) ^any { ... coerce to integer or throw exception }

Now integer would be equivalent to

     define integer {
         to coerce(specimen) ^any { integerCoerce(specimen) }
         to makeSlot(specimen) ^any {
             define value := integerCoerce(specimen)
             define Slot {
                 to getValue ^any { value }
                 to setValue(newSpecimen) ^void {
                     value := integerCoerce(newSpecimen)
                 }
             }
         }

}

However, we'd have to repeat this pattern for each type. Instead, we define "final" and "settable". Both of these are both SlotGuards and SlotGuardTemplates. What's a SlotGuardTemplate? It's a function that takes a ValueGuard as an argument and returns a SlotGuard.

     define settable {
         to run(valueGuard) ^any {
             define SlotGuard {
                 to makeSlot(specimen) ^any {
                     define value := valueGuard coerce(specimen)
                     define Slot {
                         to getValue ^any { value }
                         to setValue(newSpecimen) ^void {
                             value := valueGuard coerce(newSpecimen)
                         }
                     }
                 }
             }
         }
         to makeSlot(specimen) ^any {
             settable(any) makeSlot(specimen)
         }

}

We can define "final" by simply removing the "setValue" method from the above.

We can now define "integer" more simply as

define integerValueGuard coerce(specimen) ^any { integerCoerce(specimen) } define integer := settable(integerValueGuard)

Some examples

define x : final := expr

"x" cannot be assigned to.

define x : (final(integer)) := expr

"x" cannot be assigned to, and can only hold an integer. If expr's value does not coerce to an integer, throw an exception rather than create x.

define String := import:java.lang.String asType

define x : String := expr

"String" is bound to the Java class object representing the Java type "String". From E, all Java classes are also both ValueGuards and SlotGuards. Therefore

     define concat(x : String, y : String) ^String {
         x + y

}

blows up if you feed it a non-String, or if it tries to reveal a non-String.


So, bottom line: "^any" is no more trouble to say than "reveal" but you get a lot more mileage out of it -- you can put any ValueGuard after the "^", including user-defined ones. The SlotGuards on the parameters also serve as entry guards (or preconditions), only letting in stuff they approve of. The method body can then operate safe in the knowledge that these parameters have been so approved. The ValueGuard in the reveal position also serves as an exit guard (or postcondition) ensuring that the only value revealed by the method is one the exit guard approves of. Together they form a semi-permeable membrane (in a weak sense), guarding what goes in and out so the insides of the method can be written to safer conditions.

         Cheers,
         --MarkM