简体   繁体   English

使用java 8设计尾递归

[英]Designing tail recursion using java 8

I was trying out the following example provide in the talk to understand the tail recursion in java8. 我试图从下面的例子中提供谈话了解java8的尾递归。

@FunctionalInterface
public interface TailCall<T> {
    TailCall<T> apply();

    default boolean isComplete() {
        return false;
    }

    default T result() {
        throw new Error("not implemented");
    }

    default T get() {
        return Stream.iterate(this, TailCall::apply).filter(TailCall::isComplete)
                                                .findFirst().get().result();
    }
}

Utility class to use the TailCall 使用TailCall的实用程序类

public class TailCalls {
    public static <T> TailCall<T> call(final TailCall<T> nextcall) {
        return nextcall;
    }

    public static <T> TailCall<T> done(final T value) {
        return new TailCall<T>() {
            @Override
            public boolean isComplete() {
                return true;
            }

            @Override
            public T result() {
                return value;
            }

            @Override
            public TailCall<T> apply() {
                throw new Error("not implemented.");
            }
        };
    }
}

Here is the use of the of Tail recursion : 以下是Tail递归的用法:

public class Main {

    public static TailCall<Integer> factorial(int fact, int n) {
        if (n == 1) {
            return TailCalls.done(fact);
        } else {
            return TailCalls.call(factorial(fact * n, n-1));
        }
    }

    public static void main(String[] args) {
        System.out.println(factorial(1, 5).get());
        }
}

It worked correctly, but I feel like we don't require the TailCall::get to compute the result. 它工作正常,但我觉得我们不需要TailCall::get来计算结果。 As per my understanding we can directly compute the result using: 根据我的理解,我们可以使用以下方法直接计算结果:

System.out.println(factorial(1, 5).result());

instead of: 代替:

System.out.println(factorial(1, 5).get());

Please let me know if I am missing the gist of TailCall::get . 如果我错过了TailCall::get的要点,请告诉我。

There is a mistake in the example. 这个例子中有一个错误。 It will just preform plain recursion, without tail call optimization. 它只是预先形成普通递归,没有尾调用优化。 You can see this by adding Thread.dumpStack to the base case: 您可以通过将Thread.dumpStack添加到基本案例来看到这一点:

if (n == 1) {
    Thread.dumpStack();
    return TailCalls.done(fact);
}

The stack trace will look something like: 堆栈跟踪看起来像:

java.lang.Exception: Stack trace
    at java.lang.Thread.dumpStack(Thread.java:1333)
    at test.Main.factorial(Main.java:14)
    at test.Main.factorial(Main.java:18)
    at test.Main.factorial(Main.java:18)
    at test.Main.factorial(Main.java:18)
    at test.Main.factorial(Main.java:18)
    at test.Main.main(Main.java:8)

As you can see there are multiple calls to factorial . 正如您所看到的,有多个对factorial调用。 This means that plain recursion takes place, without the tail call optimization. 这意味着在没有尾调用优化的情况下发生普通递归。 In that case there is indeed no point in calling get since the TailCall object you get back from factorial already has the result in it. 在这种情况下,调用get确实没有意义,因为从factorial返回的TailCall对象已经有了结果。


The right way to implement this is to return a new TailCall object that defers the actual call: 实现此目的的正确方法是返回一个推迟实际调用的新TailCall对象:

public static TailCall<Integer> factorial(int fact, int n) {
    if (n == 1) {
        return TailCalls.done(fact);
    }

    return () -> factorial(fact * n, n-1);
}

If you also add the Thread.dumpStack there will be only 1 call to factorial : 如果你还添加了Thread.dumpStack ,那么只有1次调用factorial

java.lang.Exception: Stack trace
    at java.lang.Thread.dumpStack(Thread.java:1333)
    at test.Main.factorial(Main.java:14)
    at test.Main.lambda$0(Main.java:18)
    at java.util.stream.Stream$1.next(Stream.java:1033)
    at java.util.Spliterators$IteratorSpliterator.tryAdvance(Spliterators.java:1812)
    at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126)
    at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:498)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:485)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
    at java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:152)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:464)
    at test.TailCall.get(Main.java:36)
    at test.Main.main(Main.java:9)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM