A preface,

The tail call

wikipedia

In computer science, a tail call is a situation in which the last action of a function returns the result of a call to a function, i.e. the return value of the last new call is directly returned by the result of the current function. At this point, the tail-call location is called the tail-call location. An important and special case of tail calls is called tail recursion. With proper processing, the efficiency of tail-recursive functions can be greatly optimized. Tail-calls can in principle be optimized for performance (called “tail-call elimination”) by simplifying the structure of the function call stack, but whether tail-call optimization is feasible depends on how well the runtime supports such optimizations.

Tail recursion

Wikipedia:

When a function calls itself (or a function that calls itself, and so on) at the end, this is called tail recursion. Tail recursion is also a special case of recursion. Tail-recursion is a special tail-call that calls its own recursive function directly at the tail. Optimization of tail recursion is also a major reason to focus on tail calls. Tail calls are not necessarily recursive calls, but tail recursion is particularly useful and easy to implement.

Tail recursion has two more features on the basis of ordinary tail calls:

  • The function itself is called at the tail;
  • Calculations can be optimized to take up only Stack Space.

Now let’s look at tail recursion in a common language. Question: Find n factorial?

Tail recursion in Java

recursive

methods

	/** ** *@param n n
	 * @returnFactorial * /
	public static BigInteger factorialRecursion(final BigInteger n) {
		if (n.compareTo(BigInteger.ZERO) < 0) {
			throw new IllegalArgumentException();
		}
		if (n.compareTo(BigInteger.ONE) == 0) {
			return BigInteger.ONE;
		} else {
			returnfactorialRecursion(n.subtract(BigInteger.ONE)).multiply(n); }}Copy the code

Test n = 10W

   @Test
   void factorialTailRecursion(a) {
   	Examination.start();
   	BigInteger result = JRecursion.factorialTailRecursion(BigInteger.ONE, BigInteger.valueOf(10000000L));
   	System.out.println("Calculation result :" + result);
   	Examination.end();
   }

Copy the code

run

java.lang.StackOverflowError
	at com.robbendev.blog.recursion.JRecursion.factorialTailRecursion(JRecursion.java:35)
	at com.robbendev.blog.recursion.JRecursion.factorialTailRecursion(JRecursion.java:38)
	at com.robbendev.blog.recursion.JRecursion.factorialTailRecursion(JRecursion.java:38)
	at com.robbendev.blog.recursion.JRecursion.factorialTailRecursion(JRecursion.java:38)
	at com.robbendev.blog.recursion.JRecursion.factorialTailRecursion(JRecursion.java:38)
  
Copy the code

Not surprisingly, stack overflow, look at the tail recursion below.

Tail recursion

Tail recursive method

private int factorialTtailRecursion(final int result, final int n) {
        if (n == 1) {
                return result;
        } else {
                return factorialTtailRecursion(result * n,  n - 1); }}Copy the code

test

case n = 10w

java.lang.StackOverflowError
	at com.robbendev.blog.recursion.JRecursion.factorialTailRecursion(JRecursion.java:35)
	at com.robbendev.blog.recursion.JRecursion.factorialTailRecursion(JRecursion.java:38)
	at com.robbendev.blog.recursion.JRecursion.factorialTailRecursion(JRecursion.java:38)
	at com.robbendev.blog.recursion.JRecursion.factorialTailRecursion(JRecursion.java:38)
	at com.robbendev.blog.recursion.JRecursion.factorialTailRecursion(JRecursion.java:38)
  
Copy the code

Did you find it or did it overflow? Because tail recursion is just like telling the compiler to optimize, but compiler optimization not optimizing is another matter. The Java compiler does not support tail-recursive optimization, so the overflow is still an overflow.

Why doesn’t Java support it? Some online research suggests:

  1. Change the stack trace, making it more difficult to debug the program. I think one of the main goals of Java is to allow programmers to easily debug their code, and stack tracing is critical to doing that.
  2. In a highly object-oriented programming environment. Since iterations can be used instead, the language committee must decide that adding tail recursion is not worth it

At the Jdk programming language level, the Jvm can use goto to jump to the top of a method call with new parameter values. And there is no need to move stack frames or cause security conflicts (Scala,Kotlin support). However, the usage scenario is very strict, so let’s see what Kotlin does, and let’s see what Else Java can do.

Stream lazy loading + simulation

A quick look at the web shows that tail recursion can be implemented by simulating lazy loading of stack +Stream.

Use a functional interface to simulate stack frames

@FunctionalInterface
public interface TailRecursion<T> {
	/** * is used to connect recursive stack frames, lazy evaluation **@returnNext recursive stack frame */
	TailRecursion<T> apply(a);

	/** * Determine whether the current recursion ends **@returnThe default is false, since normal recursion is not finished */
	default boolean isFinished(a) {
		return false;
	}

	/** * gets the recursive result, can only be called at the end of the recursion, this is the default exception, by overwriting the utility class to get the value **@returnThe final result of the recursion */
	default T getResult(a) {
		throw new Error("Recursion not finished, call get result exception!");
	}

	/** * Perform a series of recursions as early as possible. Since there is only one stack frame, use findFirst to get the final stack frame, and then call getResult to get the final recursive value **@returnEvaluate early to get the final recursive result */
	default T invoke(a) {
		return Stream.iterate(this, TailRecursion::apply) .filter(TailRecursion::isFinished) .findFirst() .get() .getResult(); }}Copy the code

The last call to the optimization utility class

public class TailInvoke {
   /** * unified structure method to get the next recursion of the current recursion **@paramNextFrame next recursion *@param <T>       T
    * @returnThe next recursion is */
   public static <T> TailRecursion<T> call(final TailRecursion<T> nextFrame) {
   	return nextFrame;
   }

   /** * Ends the current recursion, overrides the corresponding default method value, changes the completion state to true, sets the final return result, sets the illegal recursive call **@paramValue Indicates the final recursive value *@param <T>   T
    * @returnA tail recursion of the isFinished state true, which is externally initiated by an early evaluation by calling the interface's Invoke method. * /
   public static <T> TailRecursion<T> done(T value) {
   	return new TailRecursion<T>() {
   		@Override
   		public TailRecursion<T> apply(a) {
   			throw new Error("Recursion has ended, illegal call to apply method");
   		}

   		@Override
   		public boolean isFinished(a) {
   			return true;
   		}

   		@Override
   		public T getResult(a) {
   			returnvalue; }}; }}Copy the code

Use the tail call utility class to find the factorial method

	/** * factorial calculation -- use the tail recursive interface to do **@paramFactorial The result of the current recursive stack *@paramNumber The value to be computed for the next recursion *@returnA tail-recursive interface that invokes an early evaluation to get the result */
	public static TailRecursion<BigInteger> factorialTailRecursion1(final BigInteger factorial, final BigInteger number) {
		if (number.equals(BigInteger.ONE))
			return TailInvoke.done(factorial);
		else
			return TailInvoke.call(() -> factorialTailRecursion1(factorial.multiply(number), number.subtract(BigInteger.ONE)));
	}
Copy the code

test

	@Test
   void factorialTailRecursion1(a) {
   	Examination.start();
   	BigInteger result = JRecursion.factorialTailRecursion1(BigInteger.ONE, BigInteger.valueOf(100000L)).invoke();
   	System.out.println("Calculation result :" + result);
   	Examination.end();
   }
Copy the code

No overflow, test passed.

Calculation results:282422940796034787429342157802453551847749492609122485057891808654297795090106301787255177141383116361071361173736196295 147499618312391802272607340909383242200555696886678403803773794449612683801478751119669063860449261445381113700901607668 66405407..// omit n lines--------------- Your code execution time is:3823.95Ms, memory consumption:668.82M + ! ---------------Copy the code

Third, the tail recursion in kotlin

4.1 the recursive

Recursive factorial

    /** ** *@param n n
     * @returnFactorial * /
    fun factorialRecursion(n: BigInteger): Int {
        require(n >= BigInteger.ZERO)
        return if (n.compareTo(BigInteger.ONE) == 0) {
            1
        } else {
            factorialRecursion(n.subtract(BigInteger.ONE).multiply(n))
        }
    }
Copy the code

Test n = 10 w

Overflow.

Tail recursion

code

    / tail recursive writing * * * * *@param result res
     * @param n      n
     * @return res
     */
    tailrec fun factorialTailRecursion(result: BigInteger, n: BigInteger): BigInteger? {
        return if (n == BigInteger.ONE) {
            result
        } else {
            factorialTailRecursion(result.multiply(n), n.subtract(BigInteger.ONE))
        }
    }
Copy the code

The tailrec keyword indicates that this function needs to be optimized by the compiler if it conforms to the form of tail-recursive calls.

test

Calculation results:282422940796034787429342157802453551847749492609122485057891808654297795090106301787255177141383116361071361173736196295 147499618312391802272607340909383242200555696886678403803773794449612683801478751119669063860449261445381113700901607668 664054071705659522612980419583567789090475415128711408369242515352930962606722710387442460886354543639829317477617755326 2185112647485586491818--------------- Your code execution time is:4225.34Ms, memory consumption:39.42M + ! ---------------Copy the code

Why isn’t Java working? Kotlin is working. Java can also be good, look at the test method bytecode.

  public final tailrecFib2(III)I
    // annotable parameter count: 3 (visible)
    // annotable parameter count: 3 (invisible)
   L0
    LINENUMBER 41 L0
    ILOAD 3
    IFNE L1
   L2
    LINENUMBER 42 L2
    ILOAD 1
    IRETURN
   L1
    LINENUMBER 44 L1
    ILOAD 2
    ILOAD 1
    ILOAD 2
    IADD
    ILOAD 3
    ICONST_1
    ISUB
    ISTORE 3
    ISTORE 2
    ISTORE 1
    GOTO L0
   L3
   L4
    LOCALVARIABLE this Lcom/robbendev/blog/recursion/KRecursion; L0 L4 0
    LOCALVARIABLE a I L0 L4 1
    LOCALVARIABLE b I L0 L4 2
    LOCALVARIABLE n I L0 L4 3
    MAXSTACK = 4
    MAXLOCALS = 4
Copy the code

The key code is GOTO LO and you can see that when you compile it’s going to GOTO L0, just like we said before. It’s still in the same method, so there’s no stack overflow.

4. Tail recursion in JavaScript

Tail recursion is already supported in ES6 JS, but only in strict mode. In normal mode, there are two variables inside a function that trace the call stack of the function.

  • Arguments: Returns the arguments to the function when it is called.
  • Caller: Returns the function that calls the current function.

Similar to Java, you can’t just change the stack to use the information on it.

recursive

function factorialRecursion(n) {
    if (n === 1) {
        return 1;
    } else {
        return n * factorialRecursion(n);
    }
}
factorialRecursion(100000); / / overflow
Copy the code

Tail recursion

Es6 strict schema supports tail recursion

"use strict";

function factorialTailRecursion(result, n) {
    if (n === 1) {
        return result;
    } else {
        return factorialTailRecursion(result * n, n - 1);
    }
}

factorialTailRecursion(100000); // It is running properly
Copy the code

It doesn’t really matter, though, because browsers don’t support much and don’t run code in strict mode for compatibility.

Trampoline function

function trampoline(f) {  
  while (f && f instanceof Function) {
    f = f()
  }
  return f
}

function f(n, a = 0, b = 1) {  
  if (n > 0) {
    [a, b] = [b, a + b]
    return f.bind(null, n - 1, a, b)
  } else {
    return a
  }
}


trampoline(100000); // It is running properly
Copy the code

Five, the summary

I recursively take 10w factorial Java Js Kotlin
recursive StackOverFlow StackOverFlow StackOverFlow
Tail recursion StackOverFlow Strict mode pass through
other Simulation stack frame + lazy loading Trampoline function through /

Vi. Reference materials

www.cnblogs.com/invoker-/p/… Zh.wikipedia.org/wiki/%E5%B0…

Give a thumbs up for those of you who think you have gained something. Clap brick or contact me [email protected]