是的,是的,还是的(第二部分)
因此,我相信这个答案更接近您的问题的核心-我们是否可以使 任何 递归程序具有堆栈安全性?即使递归不在尾部位置?即使宿主语言没有尾调用消除?是的。是的。而且是的-只需一个小要求...
我的第一个答案的结尾谈到了loop
作为一种评估器,然后描述了如何实现的大致想法。理论听起来不错,但我想确保该技术在实践中有效。所以我们开始吧!
非平凡程序
斐波那契数列很适合这个问题。二进制递归实现会构建一个庞大的递归树,而且每个递归调用都不在尾部位置。如果我们能正确地编写这个程序,就可以有合理的信心我们正确地实现了loop
。
这里有一个小要求:您不能调用函数进行递归。而是使用call (f, x)
代替f(x)
。
const add = (a = 0, b = 0) =>
a + b
const fib = (init = 0) =>
loop
( (n = init) =>
n < 2
? n
<del>: add (recur (n - 1), recur (n - 2))</del>
: call (add, recur (n - 1), recur (n - 2))
)
fib (10)
// => 55
但是这些
call
和
recur
函数并没有什么特别之处。它们只创建普通的JS对象。
const call = (f, ...values) =>
({ type: call, f, values })
const recur = (...values) =>
({ type: recur, values })
在这个程序中,我们有一个依赖于两个
recur
的
call
。每个
recur
都有可能生成另一个
call
和额外的
recur
。这确实是一个非平凡的问题,但实际上我们只是处理一个定义明确的递归数据结构。
编写循环
如果循环
要处理这个递归数据结构,最简单的方式是将循环
编写成递归程序。但我们不会在其他地方遇到堆栈溢出问题吗?让我们来看看!
const loop = f =>
{
const aux1 = (expr = {}, k = identity) =>
expr.type === recur
?
: expr.type === call
?
: k (expr)
return aux1 (f ())
}
所以loop
接受一个要循环的函数f
。我们期望f
在计算完成时返回普通的JS值。否则,返回call
或recur
来增加计算。
这些待办事项有点琐碎,现在让我们填写它们 -
const loop = f =>
{
const aux1 = (expr = {}, k = identity) =>
expr.type === recur
? aux (expr.values, values => aux1 (f (...values), k))
: expr.type === call
? aux (expr.values, values => aux1 (expr.f (...values), k))
: k (expr)
const aux = (exprs = [], k) =>
return aux1 (f ())
}
直观来说,aux1
(“辅助一”)就是我们在一个表达式expr
上挥舞的魔棒,然后result
会在延续中返回。换句话说 -
// evaluate expr to get the result
aux1 (expr, result => ...)
为了评估
recur
或
call
,我们必须先评估相应的
values
。我们希望能够写出类似于以下内容的东西 -
// can't do this!
const r =
expr.values .map (v => aux1 (v, ...))
return k (expr.f (...r))
接下来的...
是什么内容?我们不能像那样在.map
中调用aux1
。相反,我们需要另一种神奇的工具,可以采取表达式数组,并将结果值传递给其续集,如aux
。
// evaluate each expression and get all results as array
aux (expr.values, values => ...)
肉和土豆
好的,这可能是问题中最困难的部分。对于输入数组中的每个表达式,我们必须调用aux1
并将延续链接到下一个表达式,最后将值传递给用户提供的延续k
。
const aux = (exprs = [], k) =>
exprs.reduce
( (mr, e) =>
k => mr (r => aux1 (e, x => k ([ ...r, x ])))
, k => k ([])
)
(k)
我们最终不会使用这个,但它有助于将我们在aux中进行的操作表达为普通的reduce和append。
const cont = x =>
k => k (x)
const append = (xs, x) =>
[ ...xs, x ]
const lift2 = (f, mx, my) =>
k => mx (x => my (y => k (f (x, y))))
const aux = (exprs = [], k) =>
exprs.reduce
( (mr, e) =>
lift2 (append, mr, k => aux1 (e, k))
, cont ([])
)
将所有内容组合在一起,我们得到 -
const identity = x =>
x
const loop = f =>
{
const aux1 = (expr = {}, k = identity) =>
expr.type === recur
? aux (expr.values, values => aux1 (f (...values), k))
: expr.type === call
? aux (expr.values, values => aux1 (expr.f (...values), k))
: k (expr)
const aux = (exprs = [], k) =>
exprs.reduce
( (mr, e) =>
k => mr (r => aux1 (e, x => k ([ ...r, x ])))
, k => k ([])
)
(k)
return aux1 (f ())
}
是时候庆祝一下了 -
fib (10)
// => 55
但只有一点点 -
fib (30)
// => RangeError: Maximum call stack size exceeded
你的原始问题
在我们尝试修复loop
之前,让我们重新审视你问题中的程序foldr
,并看看如何使用loop
、call
和recur
表达它-
const foldr = (f, init, xs = []) =>
loop
( (i = 0) =>
i >= xs.length
? init
<del>: f (recur (i + 1), xs[i])</del>
: call (f, recur (i + 1), xs[i])
)
它是如何工作的?
const small =
[ 1, 2, 3 ]
const large =
Array .from (Array (2e4), (_, n) => n + 1)
foldr ((a, b) => `(${a}, ${b})`, 0, small)
foldr ((a, b) => `(${a}, ${b})`, 0, large)
好的,对于small
它可以工作,但是对于large
它会导致堆栈溢出。但这正是我们所期望的,对吗?毕竟,loop
只是一个普通的递归函数,注定会发生堆栈溢出……对吗?
在继续之前,请在您自己的浏览器中验证到此为止的结果 -
const call = (f, ...values) =>
({ type: call, f, values })
const recur = (...values) =>
({ type: recur, values })
const identity = x =>
x
const loop = f =>
{
const aux1 = (expr = {}, k = identity) =>
expr.type === recur
? aux (expr.values, values => aux1 (f (...values), k))
: expr.type === call
? aux (expr.values, values => aux1 (expr.f (...values), k))
: k (expr)
const aux = (exprs = [], k) =>
exprs.reduce
( (mr, e) =>
k => mr (r => aux1 (e, x => k ([ ...r, x ])))
, k => k ([])
)
(k)
return aux1 (f ())
}
const fib = (init = 0) =>
loop
( (n = init) =>
n < 2
? n
: call
( (a, b) => a + b
, recur (n - 1)
, recur (n - 2)
)
)
const foldr = (f, init, xs = []) =>
loop
( (i = 0) =>
i >= xs.length
? init
: call (f, recur (i + 1), xs[i])
)
const small =
[ 1, 2, 3 ]
const large =
Array .from (Array (2e4), (_, n) => n + 1)
console .log (fib (10))
console .log (foldr ((a, b) => `(${a}, ${b})`, 0, small))
console .log (foldr ((a, b) => `(${a}, ${b})`, 0, large))
反弹循环
我在转换函数为CPS并使用跳板来反弹的答案上得到了太多的答案。本答案不会过多涉及此类内容。如上所示,我们有aux1
和aux
作为CPS尾递归函数。可以通过机械方式完成以下转换。
与我们在其他答案中所做的一样,对于每个函数调用f(x)
,将其转换为call(f,x)
-
// loop : (unit -> 'a expr) -> 'a
const loop = f =>
{ // aux1 : ('a expr, 'a -> 'b) -> 'b
const aux1 = (expr = {}, k = identity) =>
expr.type === recur
? call (aux, expr.values, values => call (aux1, f (...values), k))
: expr.type === call
? call (aux, expr.values, values => call (aux1, expr.f (...values), k))
: call (k, expr)
// aux : (('a expr) array, 'a array -> 'b) -> 'b
const aux = (exprs = [], k) =>
call
( exprs.reduce
( (mr, e) =>
k => call (mr, r => call (aux1, e, x => call (k, [ ...r, x ])))
, k => call (k, [])
)
, k
)
<del>return aux1 (f ())</del>
return run (aux1 (f ()))
}
将return
包装在run
中,这是一个简化的跳板 -
const run = r =>
{ while (r && r.type === call)
r = r.f (...r.values)
return r
}
现在它是如何工作的?
const small =
[ 1, 2, 3 ]
const large =
Array .from (Array (2e4), (_, n) => n + 1)
fib (30)
foldr ((a, b) => `(${a}, ${b})`, 0, small)
foldr ((a, b) => `(${a}, ${b})`, 0, large)
通过扩展和运行下面的片段,您可以在任何JavaScript程序中见证堆栈安全递归 -
const call = (f, ...values) =>
({ type: call, f, values })
const recur = (...values) =>
({ type: recur, values })
const identity = x =>
x
const loop = f =>
{
const aux1 = (expr = {}, k = identity) =>
expr.type === recur
? call (aux, expr.values, values => call (aux1, f (...values), k))
: expr.type === call
? call (aux, expr.values, values => call (aux1, expr.f (...values), k))
: call (k, expr)
const aux = (exprs = [], k) =>
call
( exprs.reduce
( (mr, e) =>
k => call (mr, r => call (aux1, e, x => call (k, [ ...r, x ])))
, k => call (k, [])
)
, k
)
return run (aux1 (f ()))
}
const run = r =>
{ while (r && r.type === call)
r = r.f (...r.values)
return r
}
const fib = (init = 0) =>
loop
( (n = init) =>
n < 2
? n
: call
( (a, b) => a + b
, recur (n - 1)
, recur (n - 2)
)
)
const foldr = (f, init, xs = []) =>
loop
( (i = 0) =>
i >= xs.length
? init
: call (f, recur (i + 1), xs[i])
)
const small =
[ 1, 2, 3 ]
const large =
Array .from (Array (2e4), (_, n) => n + 1)
console .log (fib (30))
console .log (foldr ((a, b) => `(${a}, ${b})`, 0, small))
console .log (foldr ((a, b) => `(${a}, ${b})`, 0, large))
评估可视化
使用
foldr
评估一个简单的表达式,看看我们是否能窥探
loop
的奥秘。
const add = (a, b) =>
a + b
foldr (add, 'z', [ 'a', 'b' ])
您可以将此复制到支持括号高亮的文本编辑器中以跟随–
aux1
( call (add, recur (1), 'a')
, identity
)
aux1
( { call
, f: add
, values:
[ { recur, values: [ 1 ] }
, 'a'
]
}
, identity
)
aux
( [ { recur, values: [ 1 ] }
, 'a'
]
, values => aux1 (add (...values), identity)
)
[ { recur, values: [ 1 ] }
, 'a'
]
.reduce
( (mr, e) =>
k => mr (r => aux1 (e, x => k ([ ...r, x ])))
, k => k ([])
)
(values => aux1 (add (...values), identity))
(k => (k => (k => k ([])) (r => aux1 ({ recur, values: [ 1 ] }, x => k ([ ...r, x ])))) (r => aux1 ('a', x => k ([ ...r, x ])))) (values => aux1 (add (...values), identity))
(k => (k => k ([])) (r => aux1 ({ recur, values: [ 1 ] }, x => k ([ ...r, x ])))) (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ])))
(k => k ([])) (r => aux1 ({ recur, values: [ 1 ] }, x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...r, x ])))
(r => aux1 ({ recur, values: [ 1 ] }, x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...r, x ]))) ([])
aux1
( { recur, values: [ 1 ] }
, x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])
)
aux
( [ 1 ]
, values => aux1 (f (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))
)
[ 1 ]
.reduce
( (mr, e) =>
k => mr (r => aux1 (e, x => k ([ ...r, x ])))
, k => k ([])
)
(values => aux1 (f (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ]))))
(k => (k => k ([])) (r => aux1 (1, x => k ([ ...r, x ])))) (values => aux1 (f (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ]))))
(k => k ([])) (r => aux1 (1, x => (values => aux1 (f (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ])))
(r => aux1 (1, x => (values => aux1 (f (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([])
aux1
( 1
, x => (values => aux1 (f (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...[], x ])
)
(x => (values => aux1 (f (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...[], x ])) (1)
(values => aux1 (f (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...[], 1 ])
(values => aux1 (f (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ 1 ])
aux1
( f (...[ 1 ])
, x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])
)
aux1
( f (1)
, x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])
)
aux1
( call (add, recur (2), 'b')
, x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])
)
aux1
( { call
, f: add
, values:
[ { recur, values: [ 2 ] }
, 'b'
]
}
, x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])
)
aux
( [ { recur, values: [ 2 ] }
, 'b'
]
, values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))
)
[ { recur, values: [ 2 ] }
, 'b'
]
.reduce
( (mr, e) =>
k => mr (r => aux1 (e, x => k ([ ...r, x ])))
, k => k ([])
)
(values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ]))))
(k => (k => (k => k ([])) (r => aux1 ({ recur, values: [ 2 ] }, x => k ([ ...r, x ])))) (r => aux1 ('b', x => k ([ ...r, x ])))) (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ]))))
(k => (k => k ([])) (r => aux1 ({ recur, values: [ 2 ] }, x => k ([ ...r, x ])))) (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ])))
(k => k ([])) (r => aux1 ({ recur, values: [ 2 ] }, x => (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...r, x ])))
(r => aux1 ({ recur, values: [ 2 ] }, x => (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...r, x ]))) ([])
aux1
( { recur, values: [ 2 ] }
, x => (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...[], x ])
)
aux
( [ 2 ]
, values => aux1 (f (...values), (x => (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...[], x ])))
)
[ 2 ]
.reduce
( (mr, e) =>
k => mr (r => aux1 (e, x => k ([ ...r, x ])))
, k => k ([])
)
(values => aux1 (f (...values), (x => (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...[], x ]))))
(k => (k => k ([])) (r => aux1 (2, x => k ([ ...r, x ])))) (values => aux1 (f (...values), (x => (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...[], x ]))))
(k => k ([])) (r => aux1 (2, x => (values => aux1 (f (...values), (x => (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ])))
(r => aux1 (2, x => (values => aux1 (f (...values), (x => (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([])
aux1
( 2
, x => (values => aux1 (f (...values), (x => (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...[], x ])
)
(x => (values => aux1 (f (...values), (x => (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...[], x ])) (2)
(values => aux1 (f (...values), (x => (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...[], 2 ])
(values => aux1 (f (...values), (x => (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...[], x ])))) ([ 2 ])
aux1
( f (...[ 2 ])
, x => (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...[], x ])
)
aux1
( f (2)
, x => (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...[], x ])
)
aux1
( 'z'
, x => (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...[], x ])
)
(x => (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...[], x ])) ('z')
(r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...[], 'z' ])
(r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ 'z' ])
aux1
( 'b'
, x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...[ 'z' ], x ])
)
(x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...[ 'z' ], x ])) ('b')
(values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...[ 'z' ], 'b' ])
(values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ 'z', 'b' ])
aux1
( add (...[ 'z', 'b' ])
, x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])
)
aux1
( add ('z', 'b')
, x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])
)
aux1
( 'zb'
, x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])
)
(x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])) ('zb')
(r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], 'zb' ])
(r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ 'zb' ])
aux1
( 'a'
, x => (values => aux1 (f (...values), identity)) ([ ...[ 'zb' ], x ])
)
(x => (values => aux1 (f (...values), identity)) ([ ...[ 'zb' ], x ])) ('a')
(values => aux1 (f (...values), identity)) ([ ...[ 'zb' ], 'a' ])
(values => aux1 (f (...values), identity)) ([ 'zb', 'a' ])
aux1
( f (...[ 'zb', 'a' ])
, identity
)
aux1
( f ('zb', 'a')
, identity
)
aux1
( 'zba'
, identity
)
identity ('zba')
'zba'
闭包确实很神奇。以上,我们可以确认CPS保持了计算的平稳性:我们在每一步中都看到
aux
、
aux1
或简单的beta规约。这就是使我们能够将
loop
放在一个反弹器上的原因。
这就是我们对
call
进行双重利用的地方。我们使用
call
为我们的循环计算创建一个对象,但
aux
和
aux1
也会产生由
run
处理的
call
。我本可以(也许应该)为此制作不同的标签,但
call
足够通用,可以在两个地方使用。
所以,当我们看到
aux (...)
和
aux1 (...)
以及beta规约
(x => ...) (...)
时,我们只需将它们分别替换为
call (aux, ...)
、
call (aux1, ...)
和
call (x => ..., ...)
。将它们传递给
run
,这就是——任何形式的堆栈安全递归。就这么简单。
调优与优化
我们可以看到,虽然是一个小程序,但loop
在保持您的思维不受堆栈问题困扰方面做了大量的工作。我们还可以看到loop
不是最高效的;特别是我们注意到有大量的剩余参数和展开参数(...
),这些都是昂贵的。如果我们可以在没有它们的情况下编写loop
,我们可以期望看到巨大的内存和速度提升 -
const loop = f =>
{
const aux1 = (expr = {}, k = identity) =>
{ switch (expr.type)
{ case recur:
return call (aux, f, expr.values, k)
case call:
return call (aux, expr.f, expr.values, k)
default:
return call (k, expr)
}
}
const aux = (f, exprs = [], k) =>
{ switch (exprs.length)
{ case 0:
return call (aux1, f (), k)
case 1:
return call
( aux1
, exprs[0]
, x => call (aux1, f (x), k)
)
case 2:
return call
( aux1
, exprs[0]
, x =>
call
( aux1
, exprs[1]
, y => call (aux1, f (x, y), k)
)
)
case 3:
case 4:
default:
return call
( exprs.reduce
( (mr, e) =>
k => call (mr, r => call (aux1, e, x => call (k, [ ...r, x ])))
, k => call (k, [])
)
, values => call (aux1, f (...values), k)
)
}
}
return run (aux1 (f ()))
}
现在,我们只有在用户编写的循环或连续具有超过四(4)个参数时才使用rest/spread (
...
)。这意味着我们可以避免在最常见的情况下使用高昂的可变参数提升
.reduce
。我还注意到,与链接的三元
?:
表达式
O(n)
相比,
switch
提供了速度改进(
O(1)
)。
这使得loop
的定义变得更加复杂,但这种权衡是非常值得的。初步测量显示,速度提高了100%以上,内存减少了50%以上-
fib(30)
foldr(20000)
fib(30)
foldr(20000)
当然,有许多方法可以优化
loop
,但这个练习的重点不在于向您展示所有这些方法。
loop
是一个明确定义的纯函数,它使您可以自由地进行必要的重构。
第3部分添加:
增加循环的功能
f
变为惰性求值或从右侧迭代,否则无法进行有效的右折叠。 - Bergigo(i + 1).runCont(...)
不在尾部位置具有go
-runCont
是尾调用。 - Mulango()
运行并完成后会返回{runCont: ...}
,然后调用.runCont(...)
。 - Mulan