Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Calculator example/test #4

Merged
merged 13 commits into from
Apr 24, 2020
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*~
.vessel
*.wasm
*.did
moc
vessel
4 changes: 3 additions & 1 deletion build.sh
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
moc `./vessel sources` --package adapton src -r test/test.mo
moc `./vessel sources` --package adapton src -r test/test.mo &&\
moc `./vessel sources` --package adapton src --idl test/Calc.mo &&\
moc `./vessel sources` --package adapton src -c test/Calc.mo
2 changes: 1 addition & 1 deletion package-set.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
{
"name": "base",
"repo": "/Users/matthew/dfn/motoko-base",
"version": "master",
"version": "imports",
"dependencies": []
},
{
Expand Down
25 changes: 20 additions & 5 deletions src/adapton.mo
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/** Adapton in Motoko, as a class-based generic "functor".

This module defines a general-purpose cache and dependence graph
system. See `EvalType.EvalOps` for details about its parameters.
system. See `EvalType` module for details about its parameters.

The client of this API chooses 4 representations:
In brief, the client of this API chooses 4 representations
for a customized incremental interpter that they define:

- `Name` -- the identity of cached information; must be unique.
- `Val` -- the type of data stored in Named Refs, and produced by successfully-evaluated Closures.
Expand Down Expand Up @@ -182,15 +183,17 @@ module {
var logFlag: Bool;
var logBuf: LogEventBuf<Name, Val, Error, Closure>;
var logStack: LogBufStack<Name, Val, Error, Closure>;
// defined and supplied by the client:
evalOps: E.EvalOps<Name, Val, Error, Closure>;
var evalClosure: ?E.EvalClosure<Val, Error, Closure>;
};

// class accepts the associated operations over the 4 user-defined type params
// class accepts the associated operations over the 4 user-defined type params; See usage instructions in `EvalType` module
public class Engine<Name, Val, Error, Closure>(evalOps:E.EvalOps<Name, Val, Error, Closure>, _logFlag:Bool) {

/* Initialize */

public func init(_logFlag:Bool) : Context<Name, Val, Error, Closure> {
func init(_logFlag:Bool) : Context<Name, Val, Error, Closure> {
let _evalOps = evalOps;
{
var agent = (#editor : {#editor; #archivist});
Expand All @@ -210,6 +213,15 @@ module {

var logStack : LogBufStack<Name, Val, Error, Closure> = null;
evalOps = _evalOps;
var evalClosure = (null : ?E.EvalClosure<Val, Error, Closure>);
}
};

// Call exactly once, before any accesses; See usage instructions in `EvalType` module.
public func setEvalClosure(evalClosure:E.EvalClosure<Val, Error, Closure>) {
switch (context.evalClosure) {
case null { context.evalClosure := ?evalClosure };
case (?_) { assert false };
}
};

Expand Down Expand Up @@ -642,7 +654,10 @@ module {
c.edges := Buf.Buf(0);
c.stack := ?(nodeName, oldStack);
remBackEdges(c, thunkNode.outgoing);
let res = evalOps.closureEval(thunkNode.closure);
let res = switch (c.evalClosure) {
case null { assert false; loop { } };
case (?closureEval) { closureEval.eval(thunkNode.closure) };
};
let edges = c.edges.toArray();
c.agent := oldAgent;
c.edges := oldEdges;
Expand Down
166 changes: 166 additions & 0 deletions src/eval/Calc.mo
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import A "mo:adapton/adapton";
import E "mo:adapton/evalType";
import H "mo:base/hash";
import L "mo:base/list";
import R "mo:base/result";
import P "mo:base/prelude";

import Debug "mo:base/debug";

// example evaluator
module {
public type Name = Text;

public type Val = Int;

public type Error = {
#divByZero;
#unimplemented;
#putError : Name;
};

public type Exp = {
#num: Int;
#add: (Exp, Exp);
#sub: (Exp, Exp);
#mul: (Exp, Exp);
#div: (Exp, Exp);
#named: (Name, Exp); // record a cached result at Name
};

// simple integer-based calculator, with incremental caching
public class Calc() {

public func binOpOfExp(e:Exp)
: ?{#add;#sub;#mul;#div} {
switch e {
case (#add _) ?#add;
case (#sub _) ?#sub;
case (#mul _) ?#mul;
case (#div _) ?#div;
case _ null;
}
};

/* -- extra stuff we need -- */

func expEq(x:Exp, y:Exp) : Bool {
switch (x, y) {
case (#num(n1), #num(n2)) { n1 == n2 };
case (#named(n1, e1), #named(n2, e2)) { n1 == n2 and expEq(e1, e2) };
case (#add(e1, e2), #add(e3, e4)) { expEq(e1, e3) and expEq(e2, e4) };
case (#mul(e1, e2), #mul(e3, e4)) { expEq(e1, e3) and expEq(e2, e4) };
case (#div(e1, e2), #div(e3, e4)) { expEq(e1, e3) and expEq(e2, e4) };
case (#sub(e1, e2), #sub(e3, e4)) { expEq(e1, e3) and expEq(e2, e4) };
case _ { false };
}
};

func errorEq(x:Error, y:Error) : Bool {
switch (x, y) {
case (#divByZero, #divByZero) { true };
case (#unimplemented, #unimplemented) { true };
case (#putError(n1), #putError(n2)) { n1 == n2 };
case _ { false };
}
};

var init : Bool = false;

public func eval(e:Exp) : R.Result<Val, Error> {
if (not init) {
namedCache.setEvalClosure({eval=evalRec});
init := true;
};
evalRec(e)
};

/* -- custom DSL evaluator definition: -- */

func evalRec(e:Exp) : R.Result<Val, Error> {
switch e {
case (#num(n)) { #ok(n) };
case ( #add(_, _) // feedback to compiler design: This would be easier if I could bind vars here.
or #sub(_, _)
or #mul(_, _)
or #div(_, _) ) {
switch (evalEagerPair(e)) {
case (#err(e)) #err(e);
case (#ok((n1, n2))) {
switch (binOpOfExp(e)) {
case null { P.unreachable() };
case (?#add) { #ok(n1 + n2) };
case (?#mul) { #ok(n1 * n2) };
case (?#sub) { #ok(n1 - n2) };
case (?#div) { if (n2 == 0) { #err(#divByZero) } else #ok(n1 / n2) };
}
}
}
};
case (#named(n, e)) {
// use the name to create a Thunk within the cache
switch (namedCache.putThunk(n, e)) {
case (#err(_)) { #err(#putError(n)) };
case (#ok(n)) {
switch (namedCache.get(n)) {
case (#err(_)) { assert false; loop { } };
case (#ok(res)) { res };
}
};
}
};
/*
case _ {
#err(#unimplemented)
}
*/
}
};

func evalEagerPair(e:Exp) : R.Result<(Val, Val), Error> {
func doit(e1:Exp, e2:Exp) : R.Result<(Val, Val), Error> {
switch (evalRec(e1)) {
case (#err(e)) #err(e);
case (#ok(v1)) {
switch (evalRec(e2)) {
case (#err(e)) #err(e);
case (#ok(v2)) {
#ok((v1, v2))
}
}
}
}
};
switch e {
// redoing the pattern-match because I cannot bind vars in `or` patterns
case (#add(e1, e2)) { doit(e1, e2) };
case (#sub(e1, e2)) { doit(e1, e2) };
case (#div(e1, e2)) { doit(e1, e2) };
case (#mul(e1, e2)) { doit(e1, e2) };
case _ { P.unreachable() };
}
};

/* -- cache implementation, via adapton package -- */

var namedCache : A.Engine<Name, Val, Error, Exp> = {
let _errorEq = errorEq;
let engine = A.Engine<Name, Val, Error, Exp>
({
nameEq=func (x:Text, y:Text) : Bool { x == y };
valEq=func (x:Int, y:Int) : Bool { x == y };
errorEq=_errorEq;
closureEq=expEq;
nameHash=H.hashOfText;
cyclicDependency=func (stack:L.List<Name>, name:Name) : Error {
assert false; loop { }
}
},
true);
// not yet fully initialized (still need to do setClosureEval)
engine
};

};

}
7 changes: 7 additions & 0 deletions src/eval/lib.mo
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
Examples of incremental evaluation using the adapton engine.
*/
import C "Calc";
module {
public let Calc = C;
}
73 changes: 73 additions & 0 deletions src/evalType.mo
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import P "mo:base/prelude";
import Buf "mo:base/buf";
import Hash "mo:base/hash";
import List "mo:base/list";
import H "mo:base/hashMap";
import L "mo:base/list";

// Types defined by the interpreter client using Adapton:
module {

/*

A generic Adapton engine is parameterized by these choices,
determined by a DSL implementation and its interpreter:

1. How to represent Name, Val, Error and Closure types?
For each, how to compare two instances for equality?
How to hash Names?

2. How to evaluate a Closure to an Error or Val?


We separate the interpreter's definition into parts 1 and 2 above
in order to break the cycle of dependencies that connects the Adapton
engine's need for evaluation with the interpreter's
need to access the cache.

To resolve this cycle, the Adapton client does three steps, not one:

a. Defines the items mentioned in question 1 above, and applies the
Adapton.Engine functor to get an initial cache representation. This
representation is only half-defined, however: It still has no way to
perform Closure evaluation. Steps (b) and (c) are still needed below.

b. Defines the evaluation function required by item 2 above,
using the cache just defined in item (a). See tests dir for examples.

c. Updates the Engine from step (a) to use the evaluation function from step (b).
Again, see tests dir for examples.

Now, the evaluation function in step (b) is fully-defined,
and it is ready to use the cache provided by the adapton package.

*/

public type EvalOps<Name, Val, Error, Closure> = {

/* Once we have type components in records, move here:
type Name = _Name;
type Val = _Val;
type Error = _Error;
type Env = _Env;
type Exp = _Exp;
*/

// an equality operation for each type:
nameEq : (n1:Name, n2:Name) -> Bool;
valEq : (v1:Val, v2:Val) -> Bool;
errorEq : (err1:Error, err2:Error) -> Bool;
closureEq : (cl1:Closure, cl2:Closure) -> Bool;

// hash operations (only Name for now):
nameHash : (n:Name) -> Hash.Hash;

// abstract expression evaluation
cyclicDependency : (L.List<Name>, Name) -> Error;
};

public type EvalClosure<Val, Error, Closure> = {
eval: Closure -> {#ok:Val; #err:Error};
};

}
32 changes: 32 additions & 0 deletions test/Calc.mo
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import C "mo:adapton/eval/Calc";
import Debug "mo:base/debug";

actor {
public func run() {
// test the Calc definition imported above:
let calc = C.Calc();
let exp =
/* exp = (3 * (1 + 2)) / (5 - (4 / 2))
* || | || | | ||
* || c-----+| | e-----+|
* || ^| | ^|
* || || | ||
* |b----------++ d----------++
* | ^ ^
* | | |
* a------------+---------------+
* ^
* |
*/
#named("a",
#div(#named("b", #mul(#num(3), #named("c", #add(#num(1), #num(2))))),
#named("d", #sub(#num(5), #named("e", #div(#num(4), #num(2)))))));

Debug.print (debug_show exp);
let res1 = calc.eval(exp);
Debug.print (debug_show res1);
let res2 = calc.eval(exp);
Debug.print (debug_show res2);
};

}