Layered State Monad (#991)

This commit is contained in:
Marcin Kostrzewa 2020-07-13 17:00:15 +02:00 committed by GitHub
parent 2626bf21f2
commit be43737a34
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 625 additions and 133 deletions

View File

@ -50,14 +50,13 @@ class RecursionFixtures extends DefaultInterpreterRunner {
val sumStateTCOCode =
"""
|main = sumTo ->
| stateSum = n ->
| acc = State.get
| State.put (acc + n)
| if n == 0 then State.get else stateSum (n - 1)
|stateSum = n ->
| acc = State.get Number
| State.put Number (acc + n)
| if n == 0 then State.get Number else here.stateSum (n - 1)
|
| State.put 0
| res = stateSum sumTo
|main = sumTo ->
| res = State.run Number 0 (here.stateSum sumTo)
| res
|""".stripMargin
val sumStateTCO = getMain(sumStateTCOCode)
@ -75,18 +74,17 @@ class RecursionFixtures extends DefaultInterpreterRunner {
val nestedThunkSumCode =
"""
|doNTimes = n -> ~block ->
| block
| if n == 1 then Unit else here.doNTimes n-1 block
|
|main = n ->
| doNTimes = n -> ~block ->
| block
| if n == 1 then Unit else doNTimes n-1 block
|
| block =
| x = State.get
| State.put x+1
| x = State.get Number
| State.put Number x+1
|
| State.put 0
| doNTimes n block
| State.get
| res = State.run Number 0 (here.doNTimes n block)
| res
|""".stripMargin
val nestedThunkSum = getMain(nestedThunkSumCode)
}

View File

@ -50,7 +50,7 @@ public abstract class CaseNode extends ExpressionNode {
/**
* Forwards an error in the case's scrutinee.
*
* It is important that this is the first specialization.
* <p>It is important that this is the first specialization.
*
* @param frame the stack frame in which to execute
* @param error the error being matched against
@ -81,7 +81,8 @@ public abstract class CaseNode extends ExpressionNode {
}
CompilerDirectives.transferToInterpreter();
throw new PanicException(
ctx.get().getBuiltins().inexhaustivePatternMatchError().newInstance(object), this);
ctx.get().getBuiltins().error().inexhaustivePatternMatchError().newInstance(object),
this);
} catch (BranchSelectedException e) {
// Note [Branch Selection Control Flow]
frame.setObject(getStateFrameSlot(), e.getResult().getState());

View File

@ -1,15 +1,71 @@
package org.enso.interpreter.node.expression.builtin.state;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.dsl.*;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.Language;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.dsl.MonadicState;
import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.state.data.EmptyMap;
import org.enso.interpreter.runtime.state.data.SingletonMap;
import org.enso.interpreter.runtime.state.data.SmallMap;
import org.enso.interpreter.runtime.error.PanicException;
@BuiltinMethod(
type = "State",
name = "get",
description = "Returns the current value of monadic state.")
public class GetStateNode extends Node {
Object execute(@MonadicState Object state, Object _this) {
return state;
@ImportStatic(SmallMap.class)
@ReportPolymorphism
public abstract class GetStateNode extends Node {
static GetStateNode build() {
return GetStateNodeGen.create();
}
abstract Object execute(@MonadicState Object state, Object _this, Object key);
@Specialization(guards = {"state.getKey() == key"})
Object doSingleton(SingletonMap state, Object _this, Object key) {
return state.getValue();
}
@Specialization(
guards = {"state.getKeys() == cachedKeys", "key == cachedKey", "idx != NOT_FOUND"})
Object doMultiCached(
SmallMap state,
Object _this,
Object key,
@Cached("key") Object cachedKey,
@Cached(value = "state.getKeys()", dimensions = 1) Object[] cachedKeys,
@Cached("state.indexOf(key)") int idx) {
return state.getValues()[idx];
}
@Specialization
Object doMultiUncached(
SmallMap state,
Object _this,
Object key,
@CachedContext(Language.class) TruffleLanguage.ContextReference<Context> ctxRef) {
int idx = state.indexOf(key);
if (idx == SmallMap.NOT_FOUND) {
throw new PanicException(
ctxRef.get().getBuiltins().error().unitializedState().newInstance(key), this);
} else {
return state.getValues()[idx];
}
}
@Specialization
Object doEmpty(
EmptyMap state, Object _this, Object key, @CachedContext(Language.class) Context ctx) {
throw new PanicException(ctx.getBuiltins().error().unitializedState().newInstance(key), this);
}
@Specialization
Object doSingletonError(
SingletonMap state, Object _this, Object key, @CachedContext(Language.class) Context ctx) {
throw new PanicException(ctx.getBuiltins().error().unitializedState().newInstance(key), this);
}
}

View File

@ -1,13 +1,72 @@
package org.enso.interpreter.node.expression.builtin.state;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.dsl.*;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.Language;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.dsl.MonadicState;
import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.state.data.SingletonMap;
import org.enso.interpreter.runtime.state.data.SmallMap;
import org.enso.interpreter.runtime.error.PanicException;
import org.enso.interpreter.runtime.state.Stateful;
@BuiltinMethod(type = "State", name = "put", description = "Updates the value of monadic state.")
public class PutStateNode extends Node {
Stateful execute(@MonadicState Object state, Object _this, Object new_state) {
return new Stateful(new_state, state);
@ImportStatic(SmallMap.class)
@ReportPolymorphism
public abstract class PutStateNode extends Node {
static PutStateNode build() {
return PutStateNodeGen.create();
}
abstract Stateful execute(@MonadicState Object state, Object _this, Object key, Object new_state);
@Specialization(guards = "state.getKey() == key")
Stateful doExistingSingleton(SingletonMap state, Object _this, Object key, Object new_state) {
return new Stateful(new SingletonMap(key, new_state), new_state);
}
@Specialization(
guards = {"state.getKeys() == cachedKeys", "index != NOT_FOUND", "key == cachedKey"})
Stateful doExistingMultiCached(
SmallMap state,
Object _this,
Object key,
Object new_state,
@Cached("key") Object cachedKey,
@Cached(value = "state.getKeys()", dimensions = 1) Object[] cachedKeys,
@Cached("state.indexOf(key)") int index) {
Object[] newVals = new Object[cachedKeys.length];
System.arraycopy(state.getValues(), 0, newVals, 0, cachedKeys.length);
newVals[index] = new_state;
SmallMap newStateMap = new SmallMap(cachedKeys, newVals);
return new Stateful(newStateMap, new_state);
}
@Specialization
Stateful doMultiUncached(
SmallMap state,
Object _this,
Object key,
Object new_state,
@CachedContext(Language.class) TruffleLanguage.ContextReference<Context> ctxRef) {
int index = state.indexOf(key);
if (index == SmallMap.NOT_FOUND) {
throw new PanicException(
ctxRef.get().getBuiltins().error().unitializedState().newInstance(key), this);
} else {
return doExistingMultiCached(state, _this, key, new_state, key, state.getKeys(), index);
}
}
@Specialization
Stateful doError(
Object state,
Object _this,
Object key,
Object new_state,
@CachedContext(Language.class) Context ctx) {
throw new PanicException(ctx.getBuiltins().error().unitializedState().newInstance(key), this);
}
}

View File

@ -1,18 +1,163 @@
package org.enso.interpreter.node.expression.builtin.state;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.ImportStatic;
import com.oracle.truffle.api.dsl.ReportPolymorphism;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.dsl.MonadicState;
import org.enso.interpreter.node.callable.thunk.ThunkExecutorNode;
import org.enso.interpreter.runtime.callable.argument.Thunk;
import org.enso.interpreter.runtime.state.data.EmptyMap;
import org.enso.interpreter.runtime.state.data.SingletonMap;
import org.enso.interpreter.runtime.state.data.SmallMap;
import org.enso.interpreter.runtime.state.Stateful;
@BuiltinMethod(
type = "State",
name = "run",
description = "Runs a stateful computation in a local state environment.")
public class RunStateNode extends Node {
@ReportPolymorphism
@ImportStatic(SmallMap.class)
public abstract class RunStateNode extends Node {
static RunStateNode build() {
return RunStateNodeGen.create();
}
private @Child ThunkExecutorNode thunkExecutorNode = ThunkExecutorNode.build();
Object execute(Object _this, Object local_state, Thunk computation) {
return thunkExecutorNode.executeThunk(computation, local_state, false).getValue();
abstract Stateful execute(
@MonadicState Object state, Object _this, Object key, Object local_state, Thunk computation);
@Specialization
Stateful doEmpty(
EmptyMap state, Object _this, Object key, Object local_state, Thunk computation) {
SingletonMap localStateMap = new SingletonMap(key, local_state);
Object result = thunkExecutorNode.executeThunk(computation, localStateMap, false).getValue();
return new Stateful(state, result);
}
@Specialization(guards = {"state.getKey() == key"})
Stateful doSingletonSameKey(
SingletonMap state, Object _this, Object key, Object local_state, Thunk computation) {
SingletonMap localStateContainer = new SingletonMap(state.getKey(), local_state);
Stateful res = thunkExecutorNode.executeThunk(computation, localStateContainer, false);
return new Stateful(state, res.getValue());
}
@Specialization(
guards = {
"key == cachedNewKey",
"state.getKey() == cachedOldKey",
"cachedOldKey != cachedNewKey"
})
Stateful doSingletonNewKeyCached(
SingletonMap state,
Object _this,
Object key,
Object local_state,
Thunk computation,
@Cached("key") Object cachedNewKey,
@Cached("state.getKey()") Object cachedOldKey,
@Cached(value = "buildSmallKeys(cachedNewKey, cachedOldKey)", dimensions = 1)
Object[] newKeys) {
SmallMap localStateMap = new SmallMap(newKeys, new Object[] {local_state, state.getValue()});
Stateful res = thunkExecutorNode.executeThunk(computation, localStateMap, false);
Object newStateVal = ((SmallMap) res.getState()).getValues()[1];
return new Stateful(new SingletonMap(cachedOldKey, newStateVal), res.getValue());
}
@Specialization
Stateful doSingletonNewKeyUncached(
SingletonMap state, Object _this, Object key, Object local_state, Thunk computation) {
return doSingletonNewKeyCached(
state,
_this,
key,
local_state,
computation,
key,
state.getKey(),
buildSmallKeys(key, state.getKey()));
}
Object[] buildSmallKeys(Object k1, Object k2) {
return new Object[] {k1, k2};
}
@Specialization(
guards = {"key == cachedNewKey", "state.getKeys() == cachedOldKeys", "index == NOT_FOUND"})
Stateful doMultiNewKeyCached(
SmallMap state,
Object _this,
Object key,
Object local_state,
Thunk computation,
@Cached("key") Object cachedNewKey,
@Cached(value = "state.getKeys()", dimensions = 1) Object[] cachedOldKeys,
@Cached("state.indexOf(key)") int index,
@Cached(value = "buildNewKeys(cachedNewKey, cachedOldKeys)", dimensions = 1)
Object[] newKeys) {
Object[] newValues = new Object[newKeys.length];
System.arraycopy(state.getValues(), 0, newValues, 1, cachedOldKeys.length);
newValues[0] = local_state;
SmallMap localStateMap = new SmallMap(newKeys, newValues);
Stateful res = thunkExecutorNode.executeThunk(computation, localStateMap, false);
SmallMap resultStateMap = (SmallMap) res.getState();
Object[] resultValues = new Object[cachedOldKeys.length];
System.arraycopy(resultStateMap.getValues(), 1, resultValues, 0, cachedOldKeys.length);
return new Stateful(new SmallMap(cachedOldKeys, resultValues), res.getValue());
}
@Specialization(
guards = {"key == cachedNewKey", "state.getKeys() == cachedOldKeys", "index != NOT_FOUND"})
Stateful doMultiExistingKeyCached(
SmallMap state,
Object _this,
Object key,
Object local_state,
Thunk computation,
@Cached("key") Object cachedNewKey,
@Cached(value = "state.getKeys()", dimensions = 1) Object[] cachedOldKeys,
@Cached("state.indexOf(key)") int index) {
Object[] newValues = new Object[cachedOldKeys.length];
System.arraycopy(state.getValues(), 0, newValues, 0, cachedOldKeys.length);
newValues[index] = local_state;
SmallMap localStateMap = new SmallMap(cachedOldKeys, newValues);
Stateful res = thunkExecutorNode.executeThunk(computation, localStateMap, false);
SmallMap resultStateMap = (SmallMap) res.getState();
Object[] resultValues = new Object[cachedOldKeys.length];
System.arraycopy(resultStateMap.getValues(), 0, resultValues, 0, cachedOldKeys.length);
resultValues[index] = state.getValues()[index];
return new Stateful(new SmallMap(cachedOldKeys, resultValues), res.getValue());
}
@Specialization
Stateful doMultiUncached(
SmallMap state, Object _this, Object key, Object local_state, Thunk computation) {
int idx = state.indexOf(key);
if (idx == SmallMap.NOT_FOUND) {
return doMultiNewKeyCached(
state,
_this,
key,
local_state,
computation,
key,
state.getKeys(),
idx,
buildNewKeys(key, state.getKeys()));
} else {
return doMultiExistingKeyCached(
state, _this, key, local_state, computation, key, state.getKeys(), idx);
}
}
Object[] buildNewKeys(Object newKey, Object[] oldKeys) {
Object[] result = new Object[oldKeys.length + 1];
System.arraycopy(oldKeys, 0, result, 1, oldKeys.length);
result[0] = newKey;
return result;
}
}

View File

@ -47,9 +47,7 @@ public class Builtins {
private final AtomConstructor function;
private final AtomConstructor text;
private final AtomConstructor debug;
private final AtomConstructor syntaxError;
private final AtomConstructor compileError;
private final AtomConstructor inexhaustivePatternMatchError;
private final Error error;
private final Bool bool;
private final RootCallTarget interopDispatchRoot;
@ -70,21 +68,10 @@ public class Builtins {
any = new AtomConstructor("Any", scope).initializeFields();
number = new AtomConstructor("Number", scope).initializeFields();
bool = new Bool(language, scope);
error = new Error(language, scope);
function = new AtomConstructor("Function", scope).initializeFields();
text = new AtomConstructor("Text", scope).initializeFields();
debug = new AtomConstructor("Debug", scope).initializeFields();
syntaxError =
new AtomConstructor("Syntax_Error", scope)
.initializeFields(
new ArgumentDefinition(0, "message", ArgumentDefinition.ExecutionMode.EXECUTE));
compileError =
new AtomConstructor("Compile_Error", scope)
.initializeFields(
new ArgumentDefinition(0, "message", ArgumentDefinition.ExecutionMode.EXECUTE));
inexhaustivePatternMatchError =
new AtomConstructor("Inexhaustive_Pattern_Match_Error", scope)
.initializeFields(
new ArgumentDefinition(0, "scrutinee", ArgumentDefinition.ExecutionMode.EXECUTE));
AtomConstructor nil = new AtomConstructor("Nil", scope).initializeFields();
AtomConstructor cons =
@ -118,9 +105,6 @@ public class Builtins {
scope.registerConstructor(system);
scope.registerConstructor(runtime);
scope.registerConstructor(syntaxError);
scope.registerConstructor(compileError);
scope.registerConstructor(java);
scope.registerConstructor(thread);
@ -234,6 +218,11 @@ public class Builtins {
return bool;
}
/** @return the builtin Error types container. */
public Error error() {
return error;
}
/**
* Returns the {@code Any} atom constructor.
*
@ -252,21 +241,6 @@ public class Builtins {
return debug;
}
/** @return the builtin {@code Syntax_Error} atom constructor. */
public AtomConstructor syntaxError() {
return syntaxError;
}
/** @return the builtin {@code Compile_Error} atom constructor. */
public AtomConstructor compileError() {
return compileError;
}
/** @return the builtin {@code Inexhaustive_Pattern_Match_Error} atom constructor. */
public AtomConstructor inexhaustivePatternMatchError() {
return inexhaustivePatternMatchError;
}
/**
* Returns the builtin module scope.
*

View File

@ -0,0 +1,66 @@
package org.enso.interpreter.runtime.builtin;
import org.enso.interpreter.Language;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
import org.enso.interpreter.runtime.scope.ModuleScope;
/**
* Container for builtin Error types
*/
public class Error {
private final AtomConstructor syntaxError;
private final AtomConstructor compileError;
private final AtomConstructor inexhaustivePatternMatchError;
private final AtomConstructor unitializedState;
/**
* Creates and registers the relevant constructors.
*
* @param language the current language instance.
* @param scope the scope to register constructors in.
*/
public Error(Language language, ModuleScope scope) {
syntaxError =
new AtomConstructor("Syntax_Error", scope)
.initializeFields(
new ArgumentDefinition(0, "message", ArgumentDefinition.ExecutionMode.EXECUTE));
compileError =
new AtomConstructor("Compile_Error", scope)
.initializeFields(
new ArgumentDefinition(0, "message", ArgumentDefinition.ExecutionMode.EXECUTE));
inexhaustivePatternMatchError =
new AtomConstructor("Inexhaustive_Pattern_Match_Error", scope)
.initializeFields(
new ArgumentDefinition(0, "scrutinee", ArgumentDefinition.ExecutionMode.EXECUTE));
unitializedState =
new AtomConstructor("Uninitialized_State", scope)
.initializeFields(
new ArgumentDefinition(0, "key", ArgumentDefinition.ExecutionMode.EXECUTE));
scope.registerConstructor(syntaxError);
scope.registerConstructor(compileError);
scope.registerConstructor(inexhaustivePatternMatchError);
scope.registerConstructor(unitializedState);
}
/** @return the builtin {@code Syntax_Error} atom constructor. */
public AtomConstructor syntaxError() {
return syntaxError;
}
/** @return the builtin {@code Compile_Error} atom constructor. */
public AtomConstructor compileError() {
return compileError;
}
/** @return the builtin {@code Inexhaustive_Pattern_Match_Error} atom constructor. */
public AtomConstructor inexhaustivePatternMatchError() {
return inexhaustivePatternMatchError;
}
/** @return the builtin {@code Uninitialized_State} atom constructor. */
public AtomConstructor unitializedState() {
return unitializedState;
}
}

View File

@ -25,6 +25,7 @@ import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.callable.CallerInfo;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
import org.enso.interpreter.runtime.callable.argument.Thunk;
import org.enso.interpreter.runtime.state.data.EmptyMap;
import org.enso.interpreter.runtime.data.Vector;
import org.enso.interpreter.runtime.type.Types;
import org.enso.polyglot.MethodNames;
@ -198,7 +199,7 @@ public final class Function implements TruffleObject {
Object[] arguments,
@Cached InteropApplicationNode interopApplicationNode,
@CachedContext(Language.class) Context context) {
return interopApplicationNode.execute(function, context.getBuiltins().unit(), arguments);
return interopApplicationNode.execute(function, EmptyMap.create(), arguments);
}
}

View File

@ -0,0 +1,15 @@
package org.enso.interpreter.runtime.state.data;
import com.oracle.truffle.api.interop.TruffleObject;
/** A dummy type, denoting an empty map structure. */
public final class EmptyMap implements TruffleObject {
private static final EmptyMap INSTANCE = new EmptyMap();
private EmptyMap() {}
/** @return an instance of empty map. */
public static EmptyMap create() {
return INSTANCE;
}
}

View File

@ -0,0 +1,28 @@
package org.enso.interpreter.runtime.state.data;
/** An object representing a single key-value pairing. */
public final class SingletonMap {
private final Object key;
private final Object value;
/**
* Creates a new key-value pair.
*
* @param key the key of this pair
* @param value the value of this pair
*/
public SingletonMap(Object key, Object value) {
this.key = key;
this.value = value;
}
/** @return the key of this pair */
public Object getKey() {
return key;
}
/** @return the value of this pair */
public Object getValue() {
return value;
}
}

View File

@ -0,0 +1,59 @@
package org.enso.interpreter.runtime.state.data;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.interop.TruffleObject;
/**
* Represents an arbitrary-size map-like structure. It is low-level and only works well for small
* numbers of keys.
*/
public final class SmallMap implements TruffleObject {
private final @CompilerDirectives.CompilationFinal(dimensions = 1) Object[] keys;
private final @CompilerDirectives.CompilationFinal(dimensions = 1) Object[] values;
private static final SmallMap EMPTY = new SmallMap(new Object[0], new Object[0]);
public static final int NOT_FOUND = -1;
/** @return an empty instance of this class */
public static SmallMap empty() {
return EMPTY;
}
/**
* Creates a map with given keys and values.
*
* @param keys the keys of this map.
* @param values the values of this map. Must have the same length as {@code keys}.
*/
public SmallMap(Object[] keys, Object[] values) {
this.keys = keys;
this.values = values;
}
/**
* Returns the index of a given key in the keys array. Returns {@code NOT_FOUND} if the key is
* missing.
*
* @param key the key to lookup
* @return the key's index or {@code NOT_FOUND}
*/
@CompilerDirectives.TruffleBoundary
public int indexOf(Object key) {
for (int i = 0; i < keys.length; i++) {
if (key == keys[i]) {
return i;
}
}
return NOT_FOUND;
}
/** @return the keys in this map. */
public Object[] getKeys() {
return keys;
}
/** @return the values in this map. */
public Object[] getValues() {
return values;
}
}

View File

@ -437,6 +437,7 @@ class IrToTruffle(
setLocation(
ErrorNode.build(
context.getBuiltins
.error()
.syntaxError()
.newInstance(
"Type operators are not currently supported at runtime."
@ -479,6 +480,7 @@ class IrToTruffle(
val message = invalidBranches.map(_.message).mkString(", ")
val error = context.getBuiltins
.error()
.compileError()
.newInstance(message)
@ -738,17 +740,17 @@ class IrToTruffle(
case Error.InvalidIR(_, _, _) =>
throw new CompilerError("Unexpected Invalid IR during codegen.")
case err: Error.Syntax =>
context.getBuiltins.syntaxError().newInstance(err.message)
context.getBuiltins.error().syntaxError().newInstance(err.message)
case err: Error.Redefined.Binding =>
context.getBuiltins.compileError().newInstance(err.message)
context.getBuiltins.error().compileError().newInstance(err.message)
case err: Error.Redefined.Method =>
context.getBuiltins.compileError().newInstance(err.message)
context.getBuiltins.error().compileError().newInstance(err.message)
case err: Error.Redefined.Atom =>
context.getBuiltins.compileError().newInstance(err.message)
context.getBuiltins.error().compileError().newInstance(err.message)
case err: Error.Redefined.ThisArg =>
context.getBuiltins.compileError().newInstance(err.message)
context.getBuiltins.error().compileError().newInstance(err.message)
case err: Error.Unexpected.TypeSignature =>
context.getBuiltins.compileError().newInstance(err.message)
context.getBuiltins.error().compileError().newInstance(err.message)
}
setLocation(ErrorNode.build(payload), error.location)
}
@ -899,6 +901,7 @@ class IrToTruffle(
setLocation(
ErrorNode.build(
context.getBuiltins
.error()
.syntaxError()
.newInstance(
"Typeset literals are not yet supported at runtime."

View File

@ -93,14 +93,16 @@ class ReplTest extends InterpreterTest with BeforeAndAfter with EitherValues {
"access and modify monadic state" in {
val code =
"""
|main =
| State.put 10
|run =
| State.put Number 10
| Debug.breakpoint
| State.get
| State.get Number
|
|main = State.run Number 0 here.run
|""".stripMargin
setSessionManager { executor =>
executor.evaluate("x = State.get")
executor.evaluate("State.put (x + 1)")
executor.evaluate("x = State.get Number")
executor.evaluate("State.put Number (x + 1)")
executor.exit()
}
eval(code) shouldEqual 11

View File

@ -12,11 +12,13 @@ class StateTest extends InterpreterTest {
"be accessible from functions" in {
val code =
"""
|main =
| State.put 10
| x = State.get
| State.put x+1
| State.get
|stateful =
| State.put Number 10
| x = State.get Number
| State.put Number x+1
| State.get Number
|
|main = State.run Number 0 here.stateful
|""".stripMargin
eval(code) shouldEqual 11
@ -25,81 +27,58 @@ class StateTest extends InterpreterTest {
"be implicitly threaded through function executions" in {
val code =
"""
|Unit.incState =
| x = State.get
| State.put x+1
|inc_state =
| x = State.get Number
| State.put Number x+1
|
|main =
| State.put 0
| Unit.incState
| Unit.incState
| Unit.incState
| Unit.incState
| Unit.incState
| State.get
|run =
| here.inc_state
| here.inc_state
| here.inc_state
| here.inc_state
| here.inc_state
| State.get Number
|
|main = State.run Number 0 here.run
|""".stripMargin
eval(code) shouldEqual 5
}
"be localized with State.run" in {
val code =
"""
|main =
| State.put 20
| myBlock =
| res = State.get
| State.put 0
| res
|
| res2 = State.run 10 myBlock
| state = State.get
| res2 + state
|""".stripMargin
eval(code) shouldEqual 30
}
"work well with recursive code" in {
val code =
"""
|main =
| stateSum = n ->
| acc = State.get
| State.put acc+n
| if n == 0 then State.get else stateSum n-1
| acc = State.get Number
| State.put Number acc+n
| if n == 0 then State.get Number else stateSum n-1
|
| State.run 0 (stateSum 10)
| State.run Number 0 (stateSum 10)
|""".stripMargin
eval(code) shouldEqual 55
}
"be initialized to a Unit by default" in {
val code =
"""
|main = IO.println State.get
|""".stripMargin
eval(code)
consumeOut shouldEqual List("Unit")
}
"work with pattern matches" in {
val code =
"""
|main =
|run =
| matcher = x -> case x of
| Unit ->
| y = State.get
| State.put (y + 5)
| y = State.get Number
| State.put Number (y + 5)
| Nil ->
| y = State.get
| State.put (y + 10)
| y = State.get Number
| State.put Number (y + 10)
|
| State.put 1
| State.put Number 1
| matcher Nil
| IO.println State.get
| IO.println (State.get Number)
| matcher Unit
| IO.println State.get
| IO.println (State.get Number)
| 0
|
|main = State.run Number 0 here.run
|""".stripMargin
eval(code)
consumeOut shouldEqual List("11", "16")
@ -108,16 +87,77 @@ class StateTest extends InterpreterTest {
"undo changes on Panics" in {
val code =
"""
|main =
| panicker =
| State.put 400
| Panic.throw Unit
|panicker =
| State.put Number 400
| Panic.throw Unit
|
| State.put 5
| Panic.recover panicker
| State.get
|stater =
| State.put Number 5
| Panic.recover here.panicker
| State.get Number
|
|main = State.run Number 0 here.stater
|""".stripMargin
eval(code) shouldEqual 5
}
"localize properly with State.run when 1 key used" in {
val code =
"""
|inner = State.put Number 0
|
|outer =
| State.put Number 1
| State.run Number 2 here.inner
| State.get Number
|
|main = State.run Number 3 here.outer
|""".stripMargin
eval(code) shouldEqual 1
}
"localize properly with State.run when 2 states used" in {
val code =
"""
|type S1
|type S2
|
|inner =
| State.put S1 0
| State.put S2 0
|
|outer =
| State.put S1 1
| State.run S2 2 here.inner
| State.get S1
|
|main = State.run S1 3 here.outer
|
|""".stripMargin
eval(code) shouldEqual 0
}
"localize properly with State.run when multiple states used" in {
val code =
"""
|type S1
|type S2
|type S3
|
|inner =
| State.put S1 0
| State.put S2 0
|
|outer =
| State.put S1 1
| State.put S3 2
| State.run S2 2 here.inner
| State.get S1 + State.get S2 + State.get S3
|
|main = State.run S3 0 (State.run S2 5 (State.run S1 3 here.outer))
|
|""".stripMargin
eval(code) shouldEqual 7 // S1 = 0, S2 = 5, S3 = 2
}
}
}

View File

@ -0,0 +1,5 @@
name: Benchmarks
version: 0.0.1
license: MIT
author: enso-dev@enso.org
maintainer: enso-dev@enso.org

View File

@ -0,0 +1,40 @@
import Base.Bench_Utils
type Counter
type Sum
sum_tco = sum_to ->
summator = acc -> current ->
if current == 0 then acc else summator acc+current current-1
res = summator 0 sum_to
res
sum_co_state_body =
n = State.get Counter
acc = State.get Sum
State.put Counter n-1
State.put Sum acc+n
if n == 0 then acc else here.sum_co_state_body
sum_co_state n =
res = State.run Counter n (State.run Sum 0 here.sum_co_state_body)
res
sum_state_body n =
acc = State.get Number
State.put Number (acc + n)
if n == 0 then State.get Number else here.sum_state_body (n - 1)
sum_state = sum_to ->
res = State.run Number 0 (here.sum_state_body sum_to)
res
main =
hundred_mil = 100000000
IO.println "Measuring SumTCO"
Bench_Utils.measure (_ -> here.sum_tco hundred_mil) "sum_tco" 100 20
IO.println "Measuring State"
Bench_Utils.measure (_ -> here.sum_state hundred_mil) "sum_state" 100 20
IO.println "Measuring Co-State"
Bench_Utils.measure (_ -> here.sum_co_state hundred_mil) "sum_co_state" 100 20
IO.println "Bye."