mirror of
https://github.com/enso-org/enso.git
synced 2024-11-22 22:10:15 +03:00
Suspended atom fields are evaluated only once (#6151)
Implements #6134. # Important Notes One can define lazy atom fields as: ```haskell type Lazy Value ~x ~y ``` the evaluation of the `x` and `y` fields is then delayed until they are needed. The evaluation happens once. Then the computed value is kept in the atom for further use.
This commit is contained in:
parent
4805193428
commit
741b394b0d
@ -670,6 +670,7 @@
|
||||
- [Don't install Python component on Windows][5900]
|
||||
- [Detect potential name conflicts between exported types and FQNs][5966]
|
||||
- [Ensure calls involving warnings remain instrumented][6067]
|
||||
- [One can define lazy atom fields][6151]
|
||||
|
||||
[3227]: https://github.com/enso-org/enso/pull/3227
|
||||
[3248]: https://github.com/enso-org/enso/pull/3248
|
||||
@ -773,6 +774,7 @@
|
||||
[5900]: https://github.com/enso-org/enso/pull/5900
|
||||
[5966]: https://github.com/enso-org/enso/pull/5966
|
||||
[6067]: https://github.com/enso-org/enso/pull/6067
|
||||
[6151]: https://github.com/enso-org/enso/pull/6151
|
||||
|
||||
# Enso 2.0.0-alpha.18 (2021-10-12)
|
||||
|
||||
|
@ -1,80 +0,0 @@
|
||||
import project.Any.Any
|
||||
import project.Error.Error
|
||||
import project.Nothing.Nothing
|
||||
import project.Panic.Caught_Panic
|
||||
import project.Panic.Panic
|
||||
import project.Runtime.Ref.Ref
|
||||
|
||||
## PRIVATE
|
||||
Holds a value that is computed on first access.
|
||||
type Lazy
|
||||
## PRIVATE
|
||||
Lazy (cached_ref : Ref) (builder : Nothing -> Any)
|
||||
|
||||
## PRIVATE
|
||||
Eager (value : Any)
|
||||
|
||||
## PRIVATE
|
||||
ADVANCED
|
||||
Creates a new lazy value.
|
||||
new : Any -> Lazy
|
||||
new ~lazy_computation =
|
||||
builder _ = lazy_computation
|
||||
cached_ref = Ref.new Lazy_Not_Computed_Mark
|
||||
Lazy.Lazy cached_ref builder
|
||||
|
||||
## PRIVATE
|
||||
ADVANCED
|
||||
Creates a pre-computed lazy value.
|
||||
This can be useful if a value needs to admit the Lazy type API, but is
|
||||
known beforehand.
|
||||
new_eager value = Lazy.Eager value
|
||||
|
||||
## Returns the stored value.
|
||||
|
||||
The value will be computed on first access and cached.
|
||||
get : Any
|
||||
get self = case self of
|
||||
Lazy.Lazy cached_ref builder -> case cached_ref.get of
|
||||
Lazy_Not_Computed_Mark ->
|
||||
cached_value = Cached_Value.freeze builder
|
||||
cached_ref.put cached_value
|
||||
cached_value.get
|
||||
cached_value -> cached_value.get
|
||||
Lazy.Eager value -> value
|
||||
|
||||
## PRIVATE
|
||||
This is a special value that should never be returned from a lazy computation
|
||||
as it will prevent the lazy value from being cached.
|
||||
type Lazy_Not_Computed_Mark
|
||||
|
||||
## PRIVATE
|
||||
type Cached_Value
|
||||
## PRIVATE
|
||||
Value value
|
||||
|
||||
## PRIVATE
|
||||
Error error
|
||||
|
||||
## PRIVATE
|
||||
Panic (caught_panic : Caught_Panic)
|
||||
|
||||
## PRIVATE
|
||||
Accesses the cached value as if it was just computed - any stored errors
|
||||
or panics will be propagated.
|
||||
get : Any
|
||||
get self = case self of
|
||||
Cached_Value.Value value -> value
|
||||
Cached_Value.Error error -> Error.throw error
|
||||
Cached_Value.Panic caught_panic -> Panic.throw caught_panic
|
||||
|
||||
## PRIVATE
|
||||
Runs the provided `builder` with a `Nothing` argument, handling any
|
||||
errors or panics and saving them as a `Cached_Value`.
|
||||
freeze : (Nothing -> Any) -> Cached_Value
|
||||
freeze builder =
|
||||
save_panic caught_panic = Cached_Value.Panic caught_panic
|
||||
Panic.catch Any handler=save_panic <|
|
||||
result = Cached_Value.Value (builder Nothing)
|
||||
result.catch Any dataflow_error->
|
||||
Cached_Value.Error dataflow_error
|
@ -1,6 +1,5 @@
|
||||
from Standard.Base import all
|
||||
import Standard.Base.Errors.Illegal_State.Illegal_State
|
||||
import Standard.Base.Runtime.Lazy.Lazy
|
||||
|
||||
import project.Connection.Connection.Connection
|
||||
import project.Data.SQL_Type.SQL_Type
|
||||
@ -14,7 +13,7 @@ type SQL_Type_Reference
|
||||
|
||||
Since fetching this type requires querying the database, it is computed
|
||||
lazily and cached.
|
||||
Computed_By_Database (lazy_ref : Lazy)
|
||||
Computed_By_Database (~lazy_ref : SQL_Type)
|
||||
|
||||
## Refers to an SQL type that is overridden by the dialect's type system.
|
||||
Overridden (value : SQL_Type)
|
||||
@ -25,7 +24,7 @@ type SQL_Type_Reference
|
||||
This may perform a database query on first access.
|
||||
get : SQL_Type
|
||||
get self = case self of
|
||||
SQL_Type_Reference.Computed_By_Database lazy_ref -> lazy_ref.get
|
||||
SQL_Type_Reference.Computed_By_Database lazy_ref -> lazy_ref
|
||||
SQL_Type_Reference.Overridden value -> value
|
||||
|
||||
## PRIVATE
|
||||
@ -51,7 +50,7 @@ type SQL_Type_Reference
|
||||
columns = connection.jdbc_connection.fetch_columns statement statement_setter
|
||||
only_column = columns.first
|
||||
only_column.second
|
||||
SQL_Type_Reference.Computed_By_Database (Lazy.new do_fetch)
|
||||
SQL_Type_Reference.Computed_By_Database do_fetch
|
||||
|
||||
## PRIVATE
|
||||
Creates a new `SQL_Type_Reference` that should never be used.
|
||||
@ -61,7 +60,7 @@ type SQL_Type_Reference
|
||||
null =
|
||||
getter =
|
||||
Error.throw (Illegal_State.Error "Getting the SQL_Type from SQL_Type_Reference.null is not allowed. This indicates a bug in the Database library.")
|
||||
SQL_Type_Reference.Computed_By_Database (Lazy.new getter)
|
||||
SQL_Type_Reference.Computed_By_Database getter
|
||||
|
||||
## PRIVATE
|
||||
Turns this reference into a type override.
|
||||
|
@ -51,8 +51,25 @@ public class ListBenchmarks {
|
||||
from Standard.Base.Data.List.List import Cons, Nil
|
||||
import Standard.Base.IO
|
||||
|
||||
type Lenivy
|
||||
Nic
|
||||
Hlava ~x ~xs
|
||||
|
||||
map self fn = case self of
|
||||
Lenivy.Nic -> Lenivy.Nic
|
||||
Lenivy.Hlava x xs -> Lenivy.Hlava (fn x) (xs.map fn)
|
||||
|
||||
plus_one list = list.map (x -> x + 1)
|
||||
|
||||
leniva_suma list acc = case list of
|
||||
Lenivy.Nic -> acc
|
||||
Lenivy.Hlava x xs -> @Tail_Call leniva_suma xs acc+x
|
||||
|
||||
lenivy_generator n =
|
||||
go x v l = if x > n then l else
|
||||
@Tail_Call go x+1 v+1 (Lenivy.Hlava v l)
|
||||
go 1 1 Lenivy.Nic
|
||||
|
||||
sum list acc =
|
||||
case list of
|
||||
Nil -> acc
|
||||
@ -69,15 +86,22 @@ public class ListBenchmarks {
|
||||
this.self = module.invokeMember("get_associated_type");
|
||||
Function<String,Value> getMethod = (name) -> module.invokeMember("get_method", self, name);
|
||||
|
||||
Value longList = getMethod.apply("generator").execute(self, LENGTH_OF_EXPERIMENT);
|
||||
|
||||
this.plusOne = getMethod.apply("plus_one");
|
||||
this.sum = getMethod.apply("sum");
|
||||
|
||||
switch (benchmarkName) {
|
||||
case "mapOverList": {
|
||||
this.list = longList;
|
||||
this.oldSum = sum.execute(self, longList, 0);
|
||||
this.list = getMethod.apply("generator").execute(self, LENGTH_OF_EXPERIMENT);
|
||||
this.sum = getMethod.apply("sum");
|
||||
this.oldSum = sum.execute(self, this.list, 0);
|
||||
if (!this.oldSum.fitsInLong()) {
|
||||
throw new AssertionError("Expecting a number " + this.oldSum);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "mapOverLazyList": {
|
||||
this.list = getMethod.apply("lenivy_generator").execute(self, LENGTH_OF_EXPERIMENT);
|
||||
this.sum = getMethod.apply("leniva_suma");
|
||||
this.oldSum = sum.execute(self, this.list, 0);
|
||||
if (!this.oldSum.fitsInLong()) {
|
||||
throw new AssertionError("Expecting a number " + this.oldSum);
|
||||
}
|
||||
@ -93,6 +117,11 @@ public class ListBenchmarks {
|
||||
performBenchmark(matter);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void mapOverLazyList(Blackhole matter) {
|
||||
performBenchmark(matter);
|
||||
}
|
||||
|
||||
private void performBenchmark(Blackhole hole) throws AssertionError {
|
||||
var newList = plusOne.execute(self, list);
|
||||
var newSum = sum.execute(self, newList, 0);
|
||||
|
@ -123,7 +123,7 @@ public final class AtomConstructor implements TruffleObject {
|
||||
cachedInstance = null;
|
||||
}
|
||||
if (Layout.isAritySupported(args.length)) {
|
||||
boxedLayout = Layout.create(args.length, 0);
|
||||
boxedLayout = Layout.create(args.length, 0, args);
|
||||
}
|
||||
this.constructorFunction =
|
||||
buildConstructorFunction(language, localScope, assignments, varReads, annotations, args);
|
||||
|
@ -6,6 +6,7 @@ import com.oracle.truffle.api.nodes.ExplodeLoop;
|
||||
import org.enso.interpreter.dsl.atom.LayoutSpec;
|
||||
import org.enso.interpreter.node.expression.atom.InstantiateNode;
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
|
||||
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
|
||||
|
||||
/**
|
||||
@ -54,6 +55,7 @@ public class Layout {
|
||||
private final @CompilerDirectives.CompilationFinal(dimensions = 1) UnboxingAtom.FieldGetterNode[]
|
||||
uncachedFieldGetters;
|
||||
|
||||
private final @CompilerDirectives.CompilationFinal(dimensions = 1) ArgumentDefinition[] args;
|
||||
private final @CompilerDirectives.CompilationFinal(dimensions = 1) NodeFactory<
|
||||
? extends UnboxingAtom.FieldSetterNode>[]
|
||||
fieldSetterFactories;
|
||||
@ -69,16 +71,14 @@ public class Layout {
|
||||
int[] fieldToStorage,
|
||||
NodeFactory<? extends UnboxingAtom.FieldGetterNode>[] fieldGetterFactories,
|
||||
NodeFactory<? extends UnboxingAtom.FieldSetterNode>[] fieldSetterFactories,
|
||||
NodeFactory<? extends UnboxingAtom.InstantiatorNode> instantiatorFactory) {
|
||||
NodeFactory<? extends UnboxingAtom.InstantiatorNode> instantiatorFactory,
|
||||
ArgumentDefinition[] args) {
|
||||
this.args = args;
|
||||
this.inputFlags = inputFlags;
|
||||
this.fieldToStorage = fieldToStorage;
|
||||
this.instantiatorFactory = instantiatorFactory;
|
||||
this.fieldGetterFactories = fieldGetterFactories;
|
||||
this.uncachedFieldGetters = new UnboxingAtom.FieldGetterNode[fieldGetterFactories.length];
|
||||
for (int i = 0; i < fieldGetterFactories.length; i++) {
|
||||
this.uncachedFieldGetters[i] = fieldGetterFactories[i].getUncachedInstance();
|
||||
assert this.uncachedFieldGetters[i] != null;
|
||||
}
|
||||
this.fieldSetterFactories = fieldSetterFactories;
|
||||
this.uncachedFieldSetters = new UnboxingAtom.FieldSetterNode[fieldSetterFactories.length];
|
||||
for (int i = 0; i < fieldSetterFactories.length; i++) {
|
||||
@ -86,6 +86,15 @@ public class Layout {
|
||||
this.uncachedFieldSetters[i] = fieldSetterFactories[i].getUncachedInstance();
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < fieldGetterFactories.length; i++) {
|
||||
this.uncachedFieldGetters[i] = fieldGetterFactories[i].getUncachedInstance();
|
||||
assert this.uncachedFieldGetters[i] != null;
|
||||
if (args[i].isSuspended()) {
|
||||
this.uncachedFieldGetters[i] =
|
||||
SuspendedFieldGetterNode.build(
|
||||
this.uncachedFieldGetters[i], this.uncachedFieldSetters[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isAritySupported(int arity) {
|
||||
@ -98,7 +107,7 @@ public class Layout {
|
||||
* factories for getters, setters and instantiators.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Layout create(int arity, long typeFlags) {
|
||||
public static Layout create(int arity, long typeFlags, ArgumentDefinition[] args) {
|
||||
if (arity > 32) {
|
||||
throw new IllegalArgumentException("Too many fields in unboxed atom");
|
||||
}
|
||||
@ -137,7 +146,7 @@ public class Layout {
|
||||
var instantiatorFactory = LayoutFactory.getInstantiatorNodeFactory(numUnboxed, numBoxed);
|
||||
|
||||
return new Layout(
|
||||
typeFlags, fieldToStorage, getterFactories, setterFactories, instantiatorFactory);
|
||||
typeFlags, fieldToStorage, getterFactories, setterFactories, instantiatorFactory, args);
|
||||
}
|
||||
|
||||
public UnboxingAtom.FieldGetterNode[] getUncachedFieldGetters() {
|
||||
@ -148,6 +157,10 @@ public class Layout {
|
||||
var getters = new UnboxingAtom.FieldGetterNode[fieldGetterFactories.length];
|
||||
for (int i = 0; i < fieldGetterFactories.length; i++) {
|
||||
getters[i] = fieldGetterFactories[i].createNode();
|
||||
if (args[i].isSuspended()) {
|
||||
var setterOrNull = buildSetter(i);
|
||||
getters[i] = SuspendedFieldGetterNode.build(getters[i], setterOrNull);
|
||||
}
|
||||
}
|
||||
return getters;
|
||||
}
|
||||
@ -157,7 +170,11 @@ public class Layout {
|
||||
}
|
||||
|
||||
public UnboxingAtom.FieldGetterNode buildGetter(int index) {
|
||||
return fieldGetterFactories[index].createNode();
|
||||
var node = fieldGetterFactories[index].createNode();
|
||||
if (args[index].isSuspended()) {
|
||||
node = SuspendedFieldGetterNode.build(node, buildSetter(index));
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
public UnboxingAtom.FieldSetterNode getUncachedFieldSetter(int index) {
|
||||
@ -165,7 +182,8 @@ public class Layout {
|
||||
}
|
||||
|
||||
public UnboxingAtom.FieldSetterNode buildSetter(int index) {
|
||||
return fieldSetterFactories[index].createNode();
|
||||
var fieldSetterFactory = fieldSetterFactories[index];
|
||||
return fieldSetterFactory == null ? null : fieldSetterFactory.createNode();
|
||||
}
|
||||
|
||||
public boolean isDoubleAt(int fieldIndex) {
|
||||
@ -233,7 +251,7 @@ public class Layout {
|
||||
if (layouts.length == this.unboxedLayouts.length) {
|
||||
// Layouts stored in this node are probably up-to-date; create a new one and try to
|
||||
// register it.
|
||||
var newLayout = Layout.create(arity, flags);
|
||||
var newLayout = Layout.create(arity, flags, boxedLayout.layout.args);
|
||||
constructor.atomicallyAddLayout(newLayout, this.unboxedLayouts.length);
|
||||
}
|
||||
updateFromConstructor();
|
||||
|
@ -0,0 +1,65 @@
|
||||
package org.enso.interpreter.runtime.callable.atom.unboxing;
|
||||
|
||||
import com.oracle.truffle.api.exception.AbstractTruffleException;
|
||||
import com.oracle.truffle.api.interop.TruffleObject;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
import org.enso.interpreter.node.callable.InvokeCallableNode;
|
||||
import org.enso.interpreter.node.callable.dispatch.InvokeFunctionNode;
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
|
||||
import org.enso.interpreter.runtime.callable.atom.Atom;
|
||||
import org.enso.interpreter.runtime.callable.function.Function;
|
||||
import org.enso.interpreter.runtime.state.State;
|
||||
|
||||
/**
|
||||
* Getter node that reads a field value. If the value is a thunk the node
|
||||
* evaluates it and replaces the original lazy value with the new value.
|
||||
*/
|
||||
final class SuspendedFieldGetterNode extends UnboxingAtom.FieldGetterNode {
|
||||
@Node.Child
|
||||
private UnboxingAtom.FieldSetterNode set;
|
||||
@Node.Child
|
||||
private UnboxingAtom.FieldGetterNode get;
|
||||
@Node.Child
|
||||
private InvokeFunctionNode invoke = InvokeFunctionNode.build(
|
||||
new CallArgumentInfo[0], InvokeCallableNode.DefaultsExecutionMode.EXECUTE, InvokeCallableNode.ArgumentsExecutionMode.EXECUTE
|
||||
);
|
||||
|
||||
private SuspendedFieldGetterNode(UnboxingAtom.FieldGetterNode get, UnboxingAtom.FieldSetterNode set) {
|
||||
this.get = get;
|
||||
this.set = set;
|
||||
}
|
||||
|
||||
static UnboxingAtom.FieldGetterNode build(UnboxingAtom.FieldGetterNode get, UnboxingAtom.FieldSetterNode set) {
|
||||
return new SuspendedFieldGetterNode(get, set);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object execute(Atom atom) {
|
||||
java.lang.Object value = get.execute(atom);
|
||||
if (value instanceof Function fn && fn.isThunk()) {
|
||||
try {
|
||||
org.enso.interpreter.runtime.EnsoContext ctx = EnsoContext.get(this);
|
||||
java.lang.Object newValue = invoke.execute(fn, null, State.create(ctx), new Object[0]);
|
||||
set.execute(atom, newValue);
|
||||
return newValue;
|
||||
} catch (AbstractTruffleException ex) {
|
||||
var rethrow = new SuspendedException(ex);
|
||||
set.execute(atom, rethrow);
|
||||
throw ex;
|
||||
}
|
||||
} else if (value instanceof SuspendedException suspended) {
|
||||
throw suspended.ex;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class SuspendedException implements TruffleObject {
|
||||
final AbstractTruffleException ex;
|
||||
|
||||
SuspendedException(AbstractTruffleException ex) {
|
||||
this.ex = ex;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
package org.enso.interpreter.test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import org.enso.polyglot.MethodNames;
|
||||
import org.enso.polyglot.RuntimeOptions;
|
||||
import org.graalvm.polyglot.Context;
|
||||
import org.graalvm.polyglot.Language;
|
||||
import org.graalvm.polyglot.Source;
|
||||
import org.graalvm.polyglot.Value;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class LazyAtomFieldTest {
|
||||
private static final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
private Context ctx;
|
||||
|
||||
@Before
|
||||
public void prepareCtx() {
|
||||
this.ctx = Context.newBuilder()
|
||||
.allowExperimentalOptions(true)
|
||||
.allowIO(true)
|
||||
.allowAllAccess(true)
|
||||
.logHandler(new ByteArrayOutputStream())
|
||||
.out(out)
|
||||
.option(
|
||||
RuntimeOptions.LANGUAGE_HOME_OVERRIDE,
|
||||
Paths.get("../../distribution/component").toFile().getAbsolutePath()
|
||||
).build();
|
||||
final Map<String, Language> langs = ctx.getEngine().getLanguages();
|
||||
assertNotNull("Enso found: " + langs, langs.get("enso"));
|
||||
out.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void evaluation() throws Exception {
|
||||
final String code = """
|
||||
from Standard.Base import IO
|
||||
|
||||
type Lazy
|
||||
LazyValue ~x ~y
|
||||
|
||||
say self w = "Hello " + w.to_text
|
||||
|
||||
meaning self =
|
||||
IO.println "Computing meaning"
|
||||
v = self.x * self.y
|
||||
IO.println "Computed meaning"
|
||||
v
|
||||
|
||||
meanings =
|
||||
compute_x =
|
||||
IO.println "Computing x"
|
||||
v = 6
|
||||
IO.println "Computing x done"
|
||||
v
|
||||
|
||||
compute_y =
|
||||
IO.println "Computing y"
|
||||
v = 7
|
||||
IO.println "Computing y done"
|
||||
v
|
||||
|
||||
IO.println "Start"
|
||||
l = Lazy.LazyValue compute_x compute_y
|
||||
IO.println "Lazy value ready"
|
||||
IO.println <| l.say "World!"
|
||||
IO.println l.meaning
|
||||
IO.println <| l.say "Again!"
|
||||
IO.println l.meaning
|
||||
l.meaning
|
||||
""";
|
||||
var meanings = evalCode(code, "meanings");
|
||||
assertEquals(42, meanings.asInt());
|
||||
|
||||
String log = out.toString(StandardCharsets.UTF_8);
|
||||
var lazyReadyAndThen = log.lines().dropWhile(l -> l.contains("Lazy value ready")).collect(Collectors.toList());
|
||||
var computingX = lazyReadyAndThen.stream().filter(l -> l.contains("Computing x done")).count();
|
||||
assertEquals(log, 1, computingX);
|
||||
var computingY = lazyReadyAndThen.stream().filter(l -> l.contains("Computing y done")).count();
|
||||
assertEquals(log, 1, computingY);
|
||||
var hellos = lazyReadyAndThen.stream().filter(l -> l.startsWith("Hello")).count();
|
||||
assertEquals(log, 2, hellos);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInfiniteListGenerator() throws Exception {
|
||||
final String code = """
|
||||
import Standard.Base.IO
|
||||
|
||||
type Lazy
|
||||
Nil
|
||||
Cons ~x ~xs
|
||||
|
||||
take self n = if n == 0 then Lazy.Nil else case self of
|
||||
Lazy.Nil -> Lazy.Nil
|
||||
Lazy.Cons x xs -> Lazy.Cons x (xs.take n-1)
|
||||
|
||||
sum self acc = case self of
|
||||
Lazy.Nil -> acc
|
||||
Lazy.Cons x xs -> @Tail_Call xs.sum acc+x
|
||||
|
||||
generator n = Lazy.Cons n (Lazy.generator n+1)
|
||||
|
||||
both n =
|
||||
g = Lazy.generator 1
|
||||
// IO.println "Generator is computed"
|
||||
t = g.take n
|
||||
// IO.println "Generator is taken"
|
||||
t . sum 0
|
||||
""";
|
||||
|
||||
var both = evalCode(code, "both");
|
||||
var sum = both.execute(100);
|
||||
String log = out.toString(StandardCharsets.UTF_8);
|
||||
assertEquals(log, 5050, sum.asLong());
|
||||
}
|
||||
|
||||
private Value evalCode(final String code, final String methodName) throws URISyntaxException {
|
||||
final var testName = "test.enso";
|
||||
final URI testUri = new URI("memory://" + testName);
|
||||
final Source src = Source.newBuilder("enso", code, testName)
|
||||
.uri(testUri)
|
||||
.buildLiteral();
|
||||
var module = ctx.eval(src);
|
||||
return module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, methodName);
|
||||
}
|
||||
}
|
@ -1,12 +1,16 @@
|
||||
from Standard.Base import all
|
||||
|
||||
import Standard.Base.Runtime.Ref.Ref
|
||||
import Standard.Base.Runtime.Lazy.Lazy
|
||||
import Standard.Base.Errors.Illegal_Argument.Illegal_Argument
|
||||
|
||||
from Standard.Test import Test, Test_Suite
|
||||
import Standard.Test.Extensions
|
||||
|
||||
type Lazy
|
||||
Value ~get
|
||||
new ~computation = Lazy.Value computation
|
||||
new_eager computation = Lazy.Value computation
|
||||
|
||||
spec = Test.group "Lazy" <|
|
||||
Test.specify "should compute the result only once" <|
|
||||
ref = Ref.new 0
|
||||
|
Loading…
Reference in New Issue
Block a user