转换为尾递归
converting to tail recursion
考虑函数 g(x):
g(x) = x == 0 ? 0 : g(g(x - 1))
我认为这不是尾递归,因为对 g(x) 的调用可以分为两部分:
let y = g(x - 1);
return g(y);
如何将其转换为尾递归?
延续传球风格
您可以从直接样式转换为 continuation-passing style -
g'(x,return) =
x == 0
? return(0)
: g'(x - 1, x' => # tail
g'(x', return)) # tail
现在写g
调用g'
默认继续-
g(x) = g'(x, x' => x')
根据您使用的语言,您可能需要将 return
重命名为其他名称。 k
是一个受欢迎的选择。
另一个例子
我们可以看到这种技术应用于其他问题,例如 fib
-
# direct style
fib(x) =
x < 2
? x
: fib(x - 1) + fib(x - 2) # "+" has tail position; not fib
# continuation-passing style
fib'(x, return) =
x < 2
? return(x)
: fib'(x - 1, x' => # tail| first compute x - 1
fib(x - 2, x'' => # tail| then compute x - 2
return(x' + x''))) # tail| then return sum
fib(x) = fib'(x, z => z)
其他用途
Continuation-passing 风格在其他方面也很有用。例如,它可用于在此 search
程序中提供分支或提前退出行为 -
search(t, index, match, ifFound, notFound) =
i >= length(t)
? notFound()
: t[index] == match
? ifFound(index)
: search(t, index + 1, match, ifFound, notFound)
我们可以为每个可能的结果调用 search
并继续 -
search(["bird", "cat", "dog"],
0,
"cat",
matchedIndex => print("found at: " + matchedIndex),
() => print("not found")
)
如何
A function written in continuation-passing style takes an extra argument: an explicit "continuation"; i.e., a function of one argument — wikipedia
(* direct style *)
add(a, b) = ...
(* continuation-passing style takes extra argument *)
add(a, b, k) = ...
When the CPS function has computed its result value, it "returns" it by calling the continuation function with this value as the argument.
(* direct style *)
add(a, b) =
a + b
(* continuation-passing style "returns" by calling continuation *)
add(a, b, k) =
k(a + b) (* call k with the return value *)
That means that when invoking a CPS function, the calling function is required to supply a procedure to be invoked with the subroutine's "return" value.
(* direct style *)
print(add(5, 3)) (* 8 *)
(* continuation-passing style *)
add(5, 3, print) (* 8 *)
Expressing code in this form makes a number of things explicit which are implicit in direct style. These include: procedure returns, which become apparent as calls to a continuation; intermediate values, which are all given names; order of argument evaluation, which is made explicit; and tail calls, which simply call a procedure with the same continuation, unmodified, that was passed to the caller.
您之前可能使用过延续
如果你曾经 运行 听到有人说“回调”,他们真正的意思是延续。
"点击按钮时,继续程序event => ...
"-
document.querySelector("#foo").addEventListener("click", event => {
console.log("this code runs inside a continuation!")
})
<button id="foo">click me</button>
"读取文件内容后,继续程序(err, data) => ...
"-
import { readFile } from 'fs';
readFile('/etc/passwd', (err, data) => {
if (err) throw err;
console.log(data);
});
考虑函数 g(x):
g(x) = x == 0 ? 0 : g(g(x - 1))
我认为这不是尾递归,因为对 g(x) 的调用可以分为两部分:
let y = g(x - 1);
return g(y);
如何将其转换为尾递归?
延续传球风格
您可以从直接样式转换为 continuation-passing style -
g'(x,return) =
x == 0
? return(0)
: g'(x - 1, x' => # tail
g'(x', return)) # tail
现在写g
调用g'
默认继续-
g(x) = g'(x, x' => x')
根据您使用的语言,您可能需要将 return
重命名为其他名称。 k
是一个受欢迎的选择。
另一个例子
我们可以看到这种技术应用于其他问题,例如 fib
-
# direct style
fib(x) =
x < 2
? x
: fib(x - 1) + fib(x - 2) # "+" has tail position; not fib
# continuation-passing style
fib'(x, return) =
x < 2
? return(x)
: fib'(x - 1, x' => # tail| first compute x - 1
fib(x - 2, x'' => # tail| then compute x - 2
return(x' + x''))) # tail| then return sum
fib(x) = fib'(x, z => z)
其他用途
Continuation-passing 风格在其他方面也很有用。例如,它可用于在此 search
程序中提供分支或提前退出行为 -
search(t, index, match, ifFound, notFound) =
i >= length(t)
? notFound()
: t[index] == match
? ifFound(index)
: search(t, index + 1, match, ifFound, notFound)
我们可以为每个可能的结果调用 search
并继续 -
search(["bird", "cat", "dog"],
0,
"cat",
matchedIndex => print("found at: " + matchedIndex),
() => print("not found")
)
如何
A function written in continuation-passing style takes an extra argument: an explicit "continuation"; i.e., a function of one argument — wikipedia
(* direct style *)
add(a, b) = ...
(* continuation-passing style takes extra argument *)
add(a, b, k) = ...
When the CPS function has computed its result value, it "returns" it by calling the continuation function with this value as the argument.
(* direct style *)
add(a, b) =
a + b
(* continuation-passing style "returns" by calling continuation *)
add(a, b, k) =
k(a + b) (* call k with the return value *)
That means that when invoking a CPS function, the calling function is required to supply a procedure to be invoked with the subroutine's "return" value.
(* direct style *)
print(add(5, 3)) (* 8 *)
(* continuation-passing style *)
add(5, 3, print) (* 8 *)
Expressing code in this form makes a number of things explicit which are implicit in direct style. These include: procedure returns, which become apparent as calls to a continuation; intermediate values, which are all given names; order of argument evaluation, which is made explicit; and tail calls, which simply call a procedure with the same continuation, unmodified, that was passed to the caller.
您之前可能使用过延续
如果你曾经 运行 听到有人说“回调”,他们真正的意思是延续。
"点击按钮时,继续程序event => ...
"-
document.querySelector("#foo").addEventListener("click", event => {
console.log("this code runs inside a continuation!")
})
<button id="foo">click me</button>
"读取文件内容后,继续程序(err, data) => ...
"-
import { readFile } from 'fs';
readFile('/etc/passwd', (err, data) => {
if (err) throw err;
console.log(data);
});