org.erights.e.elang.evm
Interface Evaluator


public interface Evaluator

Untamed: Evaluates and matches Kernel-E expressions and patterns explicitly, so that this evaluation can happen, for example, remotely.

Evaluators also support the E syntactic sugar

     meta.eval(evaluator, expr)

 and

     meta <- eval(evaluator, expr)
 
as a convenience for evaluating the expression in the lexical scope of the caller.

E is an expression/pattern language. This means that execution proceeds by evaluating expressions in a lexical scope, and matching patterns in a lexical scope against a specimen (an arbitrary value which the pattern may match). Expressions and patterns may recursively contain both expressions and patterns. The result of matching a pattern against a specimen is not just an answer -- "Did the specimen match?" -- but also bindings of values (typically extracted from the specimen) to variables named in the pattern. Indeed, this is the only form of variable definition in E.

An E program is an fully ordered left-to-right tree of expressions and patterns, with a set of nested scope boxes statically imposed on this tree by particular types of expressions and patterns. When a pattern defines a variable, this name is in scope left to right from the point of definition until the end of its containing scope box.

Putting it all together, a node (an expression or pattern) has two scope-based interfaces to the outside world:

In one crucial way, the Evaluator is less general than the notion of evaluation defined by Kernel-E: To accomodate remote execution, bindingsIn and bindingsOut must represent only final variables, since they are a ConstMap mapping these names to values, rather than settable locations. While this difference is visible to the Kernel-E programmer, it can be made largely invisible to the E programmer. Except for variables recognized to fall into some special cases, the Deslotifying compilation phase transforms, for example, the mutable variable "foo" into corresponding final variable "foo__Slot", bound to an explicit Slot object holding the value of the "foo" variable. Use of "foo" as a rValue then becomes "foo__Slot.getValue()" and assignment becomes "foo__Slot.setValue(newValue)". Except for outer (ie, top-level) mutable scopes (used in the interactive read-eval-print loop, "rune"), all non-final variables and assignment statements could have been transformed away this way. (We chose not to in general in order to better support compilation, and because of the needs of the interactive read-eval-print loop.) However, when transforming for use with an Evaluator, all the in and out variables must be so transformed.

The syntactic sugar

     meta.eval(evaluator, expr)
 
expands into a call to evaluator to evaluate the provided expression in the lexical context of this sugar. Assuming the evaluator evaluates the expression according to the normal rules, then the expression evaluation should have the same effects it normally would have had. The Deslotifying transformation of the E compiler must turn all the variables in and out of expr into final variables, as explained above. For example,
     var x := 3
     def y := 7
     ... x ... x := 4 ... y ...
     ... meta.eval(visualizingEvaluator, x ** y =~ pow) ...
     ... pow ...

 expands to

     def x__Slot := settable.makeSlot(3)
     def y := 7
     ... x__Slot ... (x__Slot.setValue(4)) ... y ...
     def [pow, pow__Resolver] := Ref.promise()
     ... visualizingEvaluator.eval(e`x__Slot.getValue() ** y =~ pow`,
                                   ["x__Slot" => x__Slot, "y" => y],
                                   true,
                                   ["pow" => pow_Resolver]) ...
     ... pow ...
 
If the "meta.eval(...)" occurs in a context where the value it evaluates to is statically seen as unneeded, the "true" above would instead be "false".

If pow were defined as var pow, it would be transformed into the final variable pow__Slot.

Similarly

     meta <- eval(evaluator, expr)
 
expands into an eventual send to evaluator to eventually evaluate the provided expression in the lexical context of this sugar. For example
     meta <- eval(remoteEvaluator, def powOverThere(x) :any { x**2 })
     def y := powOverThere <- (3)

 expands to

     def [powOverThere, powOverThere__Resolver] := Ref.promise()
     remoteEvaluator <- eval(e`def powOverThere(x) :any { x**2 }`,
                             [].asMap(),
                             false,
                             ["powOverThere" => powOverThere__Resolver])
     def y := powOverThere <- (3)
 
The powOverThere function is defined on the remote machine or whatever, but is locally named "powOverThere". If this machine is VatA and the remote machine is VatC, we could now hand powOverThere to an object on VatB, who would then obtain a reference directly connected to VatC. If VatA then goes off-line, VatB would still be able to use powOverThere on VatC.

With the eventual form, the expression evaluates in the current lexical scope, just as if it were being implicitly evaluated, but it evaluates at a later time in its own turn, and potentially in another vat. If this appears in a context where its value might be used, it evaluates to a promise for the outcome of evaluating the expression, and the third argument of the expansion would be "true". Because of the delay, any Ejectors from the containing context will already be used up, so the expression cannot perform a non-local escape to an escape clause that has already exited (ie, Ejectors are dynamic-extent continuations).

If the evaluation is remote, then all bindings in and out will be as transformed by passage through the Pluribus. The non-obvious implication of this is that any use or assignment of any variable transformed by Deslotifying will fail if the resulting Slot is PassByProxy (the default). When using "meta <- eval(...)" to do remote evaluation, all in and out variables must either be final, or use Slot types that are PassByCopy or PassByConstruction (like the lamportSlot).

Author:
Mark S. Miller

Method Summary
 Object eval(EExpr eExpr, ConstMap bindingsIn, boolean forValue, ConstMap bindingsOut)
          Enabled: Evaluates an E expression in a provided lexical scope.
 Object[] evalToSingleton(EExpr eExpr, ConstMap bindingsIn, boolean forValue, ConstMap bindingsOut)
          Enabled: Just like eval(), except that, when evaluating for a value, evalToSingleton() returns a singleton list containing the value.
 boolean matchBind(Pattern pattern, ConstMap bindingsIn, boolean forTest, ConstMap bindingsOut, Object specimen)
          Enabled: Matches pattern against specimen, either failing or producing bindings.
 

Method Detail

eval

public Object eval(EExpr eExpr,
                   ConstMap bindingsIn,
                   boolean forValue,
                   ConstMap bindingsOut)
            throws Throwable
Enabled: Evaluates an E expression in a provided lexical scope.

When invoked asynchronously ("<-"), the invoker of eval() cannot distinguish between eval() evaluating to a broken reference as a value, vs eval() throwing a problem. use evalToSingleton() when you want to avoid this ambiguity. (Note: This ambiguity will often be desired. The "meta <- eval(...)" purposely produces this ambiguity.)

Parameters:
eExpr - The expression to be evaluated.
bindingsIn - Provides bindings for those variables used freely in eExpr, other than those defined in the safe scope (like "true").
forValue - Says whether anyone cares about the value this eExpr evaluates to. If false, eval() should evaluate for effect only and return null.
bindingsOut - Provides a mapping from variable names to Resolvers for those variable names that eExpr defines, and that some expression in the successor scope uses.
Returns:
If forValue is true, eval() returns the value that eExpr evaluates to. If forValue is false, then eval() should return null, and its caller should ignore the return value, whatever it is.
Throws:
Throwable - If eExpr exits non-locally, then eval() performs the same non-local exit. The two kinds of non-local exit are throwing a problem (Throwable) and Ejecting. Ejecting only works when the Ejector is still good, which can only happen when eval() is invoked synchronously.

evalToSingleton

public Object[] evalToSingleton(EExpr eExpr,
                                ConstMap bindingsIn,
                                boolean forValue,
                                ConstMap bindingsOut)
                         throws Throwable
Enabled: Just like eval(), except that, when evaluating for a value, evalToSingleton() returns a singleton list containing the value.

This way an asynchronous invoker ("<-") can distinguish between a successful evaluation to a broken reference as a value vs a thrown problem.

Throwable

matchBind

public boolean matchBind(Pattern pattern,
                         ConstMap bindingsIn,
                         boolean forTest,
                         ConstMap bindingsOut,
                         Object specimen)
                  throws Throwable
Enabled: Matches pattern against specimen, either failing or producing bindings.

Unlike eval(), no syntactic sugar is currently defined for matchBind().

Parameters:
pattern - The pattern to be matched against the specimen.
bindingsIn - Provides bindings for those variables used freely in pattern, other than those defined in the safe scope (like "true").
forTest - Says whether this should indicate failure by returning false. If forTest, then on failure all the bindingsOut must be broken with a problem report explaining the reason for match failure. If forTest is false, then the problem report is thrown on failure, and bindingsOut should be ignored.
bindingsOut - Provides a mapping from variable names to Resolvers for those variable names that pattern defines, and that some expression or pattern in the successor scope uses. The values of these 'out' variables are typically values extracted from specimen by matching.
specimen - The object to be matched against the pattern.
Returns:
If forTest is true, matchBind() returns whether the match succeeded. If forTest is false, then matchBind() only returns true, since if the match fails matchBind() throws rather than returning.
Throws:
Throwable - If pattern exits non-locally, then matchBind() performs the same non-local exit. The two kinds of non-local exit are throwing a problem (Throwable) and Ejecting. Also, if forTest is false and the match fails, matchBind() throws a problem explaining the failure.


comments?