From 7c9f84d6c0f579d18d88a3d9f60fe798474f36ee Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Giraudeau Date: Sat, 17 Mar 2018 22:18:30 +0100 Subject: [PATCH] Stack-safe evaluation of nested Lazy thunks. Fix #72. --- .../main/java/org/derive4j/example/Bench.java | 18 +++++++----- .../main/java/org/derive4j/example/List.java | 10 ++++--- .../processor/LazyConstructorDerivator.java | 29 +++++++++++++++---- 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/examples/src/main/java/org/derive4j/example/Bench.java b/examples/src/main/java/org/derive4j/example/Bench.java index ce38b67..26ff497 100644 --- a/examples/src/main/java/org/derive4j/example/Bench.java +++ b/examples/src/main/java/org/derive4j/example/Bench.java @@ -33,18 +33,20 @@ public class Bench { public static void main(String[] args) { - // Average time after 200 iterations: 17.869925 ms - timed(() -> List.range(0, COUNT).length()); + // Average time after 200 iterations: 44.041202 ms + timed(() -> List.range(0, COUNT).filter(i -> i >= 100000).length()); - // Average time after 200 iterations: 26.725065 ms - timed(() -> io.vavr.collection.Stream.range(0, COUNT).length()); + // Average time after 200 iterations: 55.995317 ms + timed(() -> io.vavr.collection.Stream.range(0, COUNT).filter(i -> i >= 100000).length()); - // Average time after 200 iterations: 24.768288 ms - timed(() -> fj.data.Stream.range(0, COUNT).length()); + // Average time after 200 iterations: 49.114037 ms + timed(() -> fj.data.Stream.range(0, COUNT).filter(i -> i >= 100000).length()); - // Average time after 200 iterations: 4.771267 ms - timed(() -> java.util.stream.Stream.iterate(0, i -> i + 1).limit(COUNT).reduce(0, (i1, i2) -> i1 + 1)); + // Average time after 200 iterations: 6.264414 ms + timed(() -> java.util.stream.Stream.iterate(0, i -> i + 1).limit(COUNT).filter(i -> i >= 100000).reduce(0, + (i1, i2) -> i1 + 1)); + // Average time after 200 iterations: 6.911543 ms timed(() -> Stream.range(0, COUNT).length()); } diff --git a/examples/src/main/java/org/derive4j/example/List.java b/examples/src/main/java/org/derive4j/example/List.java index 0b1d0c5..b79de43 100644 --- a/examples/src/main/java/org/derive4j/example/List.java +++ b/examples/src/main/java/org/derive4j/example/List.java @@ -84,8 +84,10 @@ public final List append(final List list) { } public final List filter(Predicate p) { - - return lazy(() -> list(Lists::nil, (h, tail) -> p.test(h) ? cons(h, tail.filter(p)) : tail.filter(p))); + Function, List> filter = Lists.cata( + Lists::nil, + (a, tail) -> p.test(a) ? cons(a, lazy(tail)) : lazy(tail)); + return lazy(() -> filter.apply(this)); } public final List bind(Function> f) { @@ -136,7 +138,6 @@ public void accept(A a) { } public final int length() { - return foldLeft((i, a) -> i + 1, 0); } @@ -146,7 +147,8 @@ public final B foldRight(final BiFunction, B> f, final B zero } public static void main(String[] args) { - List naturals = naturals().take(100); + List naturals = naturals().take(20000).filter(i -> i > 10000).take(100); + System.out.println(naturals.length()); List naturals2 = naturals().take(100).map(i -> i - 1); Lists.listShow(Show.intShow).println(naturals); System.out.println(Lists.listEqual(Equal.intEqual).eq(naturals, naturals)); diff --git a/processor/src/main/java/org/derive4j/processor/LazyConstructorDerivator.java b/processor/src/main/java/org/derive4j/processor/LazyConstructorDerivator.java index 75c5734..00f21a9 100644 --- a/processor/src/main/java/org/derive4j/processor/LazyConstructorDerivator.java +++ b/processor/src/main/java/org/derive4j/processor/LazyConstructorDerivator.java @@ -18,10 +18,12 @@ */ package org.derive4j.processor; +import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.TypeVariableName; @@ -75,7 +77,10 @@ public DeriveResult derive(AlgebraicDataType adt) { .map(TypeVariableName::get) .collect(Collectors.toList()); - String className = "Lazy"; + ClassName className = ClassName.bestGuess("Lazy"); + TypeName lazyTypeName = typeVariableNames.isEmpty() + ? className + : ParameterizedTypeName.get(className, typeVariableNames.toArray(new TypeName[0])); TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder(className) .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) .addTypeVariables(typeVariableNames) @@ -89,11 +94,25 @@ public DeriveResult derive(AlgebraicDataType adt) { .addModifiers(Modifier.PRIVATE, Modifier.SYNCHRONIZED) .returns(typeName) .addCode(CodeBlock.builder() - .addStatement("$T e = expression", lazyArgTypeName) - .beginControlFlow("if (e != null)") - .addStatement("evaluation = e.$L", f0.sam()) - .addStatement("expression = null") + .addStatement("$T lazy = this", lazyTypeName) + .beginControlFlow("while (true)") + .addStatement("$T expr = lazy.expression", lazyArgTypeName) + .beginControlFlow("if (expr == null)") + .addStatement("evaluation = lazy.evaluation", f0.sam()) + .addStatement("break") + .endControlFlow() + .beginControlFlow("else") + .addStatement("$T eval = expr.$L", typeName, f0.sam()) + .beginControlFlow("if (eval instanceof $T)", className) + .addStatement("lazy = ($T) eval", lazyTypeName) + .endControlFlow() + .beginControlFlow("else") + .addStatement("evaluation = eval") + .addStatement("break") .endControlFlow() + .endControlFlow() + .endControlFlow() + .addStatement("expression = null") .addStatement("return evaluation") .build()) .build())