简体   繁体   English

如何防止尾递归函数反转List的顺序?

[英]How can I prevent a tail recursive function from reversing the order of a List?

I am experimenting with the functional List type and structural sharing. 我正在尝试功能List类型和结构共享。 Since Javascript doesn't have a Tail Recursive Modulo Cons optimization, we can't just write List combinators like this, because they are not stack safe: 由于Javascript没有Tail Recursive Modulo Cons优化,我们不能像这样编写List组合器,因为它们不是堆栈安全的:

 const list = [1, [2, [3, [4, [5, []]]]]]; const take = n => ([head, tail]) => n === 0 ? [] : head === undefined ? [] : [head, take(n - 1) (tail)]; console.log( take(3) (list) // [1, [2, [3, []]]] ); 

Now I tried to implement take tail recursively, so that I can either rely on TCO (still an unsettled Promise in Ecmascript) or use a trampoline (omitted in the example to keep things simple): 现在我尝试递归地实现take tail,这样我就可以依赖TCO(在Ecmascript中仍然是一个未解决的Promise )或使用trampoline(在示例中省略以保持简单):

 const list = [1, [2, [3, [4, [5, []]]]]]; const safeTake = n => list => { const aux = (n, acc, [head, tail]) => n === 0 ? acc : head === undefined ? acc : aux(n - 1, [head, acc], tail); return aux(n, [], list); }; console.log( safeTake(3) (list) // [3, [2, [1, []]]] ); 

This works but the new created list is in reverse order. 这有效但新创建的列表的顺序相反。 How can I solve this issue in a purely functional manner? 如何以纯粹的功能性方式解决这个问题?

One way to prevent the list from reversing is to use continuation passing style. 阻止列表反转的一种方法是使用延续传递样式。 Now just put it on a trampoline of your choice... 现在就把它放在你选择的蹦床上......

 const None = Symbol () const identity = x => x const safeTake = (n, [ head = None, tail ], cont = identity) => head === None || n === 0 ? cont ([]) : safeTake (n - 1, tail, answer => cont ([ head, answer ])) const list = [ 1, [ 2, [ 3, [ 4, [ 5, [] ] ] ] ] ] console.log (safeTake (3, list)) // [ 1, [ 2, [ 3, [] ] ] ] 

Here it is on a trampoline 这是在蹦床上

const None =
  Symbol ()

const identity = x =>
  x

const call = (f, ...values) =>
  ({ tag: call, f, values })

const trampoline = acc =>
{
  while (acc && acc.tag === call)
    acc = acc.f (...acc.values)
  return acc
}

const safeTake = (n = 0, xs = []) =>
{
  const aux = (n, [ head = None, tail ], cont) =>
    head === None || n === 0
      ? call (cont, [])
      : call (aux, n - 1, tail, answer =>
          call (cont, [ head, answer ]))
  return trampoline (aux (n, xs, identity))
}

const list =
  [ 1, [ 2, [ 3, [ 4, [ 5, [] ] ] ] ] ]

console.log (safeTake (3, list))
// [ 1, [ 2, [ 3, [] ] ] ] 

Laziness gives you tail recursion modulo cons for free. 懒惰为您提供免费的尾递归模数。 Hence, the obvious solution is to use thunks. 因此,显而易见的解决方案是使用thunk。 However, we don't just want any kind of thunk. 但是,我们不只是想要任何一种thunk。 We want a thunk for an expression in weak head normal form . 我们想要以弱头正常形式表达一个thunk。 In JavaScript, we can implement this using lazy getters as follows: 在JavaScript中,我们可以使用惰性getter实现它,如下所示:

 const cons = (head, tail) => ({ head, tail }); const list = cons(1, cons(2, cons(3, cons(4, cons(5, null))))); const take = n => n === 0 ? xs => null : xs => xs && { head: xs.head, get tail() { delete this.tail; return this.tail = take(n - 1)(xs.tail); } }; console.log(take(3)(list)); 

There are lots of advantages to using lazy getters: 使用懒惰的getter有很多好处:

  1. Normal properties and lazy properties are used in the same way. 正常属性和惰性属性以相同的方式使用。
  2. You can use it to create infinite data structures. 您可以使用它来创建无限的数据结构。
  3. You don't have to worry about blowing up the stack. 你不必担心炸毁堆栈。

Hope that helps. 希望有所帮助。

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

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