Call optimization [rpm] Last

Transfer: http://www.ruanyifeng.com/blog/2015/04/tail-call.html

Last call (Tail Call) is an important concept in functional programming, this article describes its meaning and usage.

First, what is the tail call?

The concept is very simple tail call, words can make it clear, is that the last step is to call a function in another function.


function f(x){ return g(x); } 

In the above code, the final step is to call the function f is a function g, which is called a tail call.

The following two cases do not belong to tail call.


// 情况一
function f(x){ let y = g(x); return y; }  // 情况二 function f(x){ return g(x) + 1; } 

In the above code, one case where after the call the function g, as well as other operations, it is not considered a tail call, even if exactly the same semantics. Case 2 also belong to the call as well as operation, even if written in a single line.

Last function calls do not necessarily appear in the tail, as long as the last step you can.


function f(x) { if (x > 0) { return m(x) } return n(x); } 

Code above, m and n are all a function of tail call, because they are a function of the final step f.

Second, tail call optimization

Last call to call different from other reason, is that it's a special call sites.

We know that the function call will form a "call record" in memory, also known as "call frame" (call frame), save the location information to call and internal variables. If you call a function within the function A is B, then the top call record A, B will form a recording of the call. B until the end of the run, the results are returned to the A, B calling records will disappear. If the function inside B also call C functions, it is also a call stack record C, and so on. All call records, form a "call stack" (Call Stack).

Since tail call is the last step function, there is no need to retain the outer function call record, as position information call, the internal variables and the like will not be needed again, as long as the call record directly with the inner function, a function of the outer substituted call recording on it.


function f() { let m = 1; let n = 2; return g(m + n); } f();  // 等同于 function f() { return g(3); } f();  // 等同于 g(3); 

In the above code, if the function g is not a tail call, it is necessary to save the value of the function f m and n are internal variables, call location information, etc. g. But because after calling g, the function f is over, so to perform the last step, you can even delete f () call records, leaving only the g (3) of the call record.

This is called "tail call optimization" (Tail call optimization), that is, retaining only the inner call recording function. If all the functions are tail calls, you can complete each execution done, only one call record, which will greatly save memory. This is the meaning of "call optimization tail".

Third, tail recursion

Function calls itself is called recursion. If the tail call itself, it is called tail recursion.

Recursion is very memory-intensive, because of the need to save hundreds or thousands of calls simultaneously record, it is prone to "stack overflow" error (stack overflow). But for tail recursion, because there is only one call record, it will never be "stack overflow" error occurred.


function factorial(n) { if (n === 1) return 1; return n * factorial(n - 1); } factorial(5) // 120 

The above code is a factorial function, calculate the factorial of n, n-up need to save the call record, the complexity of O (n).

If the rewritten tail recursion, retaining only one call record, the complexity of O (1).


function factorial(n, total) { if (n === 1) return total; return factorial(n - 1, n * total); } factorial(5, 1) // 120 

Thus, the "tail call optimization" of recursion is significant, so some functional programming language it is written in a language specification. ES6, too, for the first time clear that the realization of all ECMAScript, must deploy "tail call optimization." That is, in ES6, as long as the use of tail recursion, stack overflow does not occur, the relative memory savings.

Fourth, the rewrite recursive function

Implement tail recursion, often we need to rewrite recursive function to ensure that only the last step calls itself. Way to do this is to put all the internal variables used rewrite function parameters. Such as the above example, a factorial function factorial need to use intermediate variable total, then this intermediate variable is rewritten to a parameter. The disadvantage of this is not very intuitive at first sight difficult to see why the factorial of 5, need to pass two parameters 5 and 1?

Two ways to solve this problem. One method outside tail recursive function, then a normal function of the form.


function tailFactorial(n, total) { if (n === 1) return total; return tailFactorial(n - 1, n * total); } function factorial(n) { return tailFactorial(n, 1); } factorial(5) // 120 

By the above code in the form of a normal factorial function factorial, tail recursive function calls tailFactorial, looks more normal.

Functional programming a concept called curried (as currying), meaning that the transfer function parameters into a single multi-parameter form. Here you can also use currying.


function currying(fn, n) { return function (m) { return fn.call(this, m, n); }; } function tailFactorial(n, total) { if (n === 1) return total; return tailFactorial(n - 1, n * total); } const factorial = currying(tailFactorial, 1); factorial(5) // 120 

上面代码通过柯里化,将尾递归函数 tailFactorial 变为只接受1个参数的 factorial 。

第二种方法就简单多了,就是采用ES6的函数默认值。


function factorial(n, total = 1) { if (n === 1) return total; return factorial(n - 1, n * total); } factorial(5) // 120 

上面代码中,参数 total 有默认值1,所以调用时不用提供这个值。

总结一下,递归本质上是一种循环操作。纯粹的函数式编程语言没有循环操作命令,所有的循环都用递归实现,这就是为什么尾递归对这些语言极其重要。对于其他支持"尾调用优化"的语言(比如Lua,ES6),只需要知道循环可以用递归代替,而一旦使用递归,就最好使用尾递归。

([说明] 本文摘自我写的《ECMAScript 6入门》

五、严格模式

ES6的尾调用优化只在严格模式下开启,正常模式是无效的。

这是因为在正常模式下,函数内部有两个变量,可以跟踪函数的调用栈。

  • arguments:返回调用时函数的参数。
  • func.caller:返回调用当前函数的那个函数。

尾调用优化发生时,函数的调用栈会改写,因此上面两个变量就会失真。严格模式禁用这两个变量,所以尾调用模式仅在严格模式下生效。

六、参考链接

(完)

Guess you like

Origin www.cnblogs.com/sugar-tomato/p/11262669.html