【问题标题】:Functional construction of a List from Pair-s从 Pair-s 构建列表的功能
【发布时间】:2020-05-31 04:44:09
【问题描述】:

我尝试从原始 Pair 数据构造一个列表(请参阅 implementation 了解完整实现) 构造函数使用递归,因此受到堆栈空间限制的影响。一种可能的解决方案是传递(数组,递减数组索引,递增列表)并保持状态作为递归调用的一部分,但这仍然在运行时使用空间。

有没有办法实现这个尾递归?我在 Node 中运行它。

// listIter::[xs] -> List[xs]
export function listIter(xs) {
    const construct = (list, index) => {
        return (index === -1) ?
            list :
            construct(pair(xs[index], list), index - 1);
    }

    return construct(Pair.empty(), xs.length - 1);
}

【问题讨论】:

  • 上次我检查 Node 由于一些技术原因删除了尾递归优化。我不知道他们是否恢复了它。只是需要注意的一点 - 尾递归可能无法解决问题。您可以考虑使用蹦床作为替代方案。
  • 你算法中的递归步骤在尾部,对吧?
  • @bob 递归步骤是逐步构建列表参数。这是以“迭代”方式构建的。
  • 是的,它在前进的道路上构建了数据结构。如果我没记错的话,从技术上讲,它是递归累加器样式或核心递归。无论哪种方式,您都需要在运行时前进或后退的空间。
  • @Ghita 一个of 函数按照惯例和in the js fp community 只接受一个参数。如果要创建静态工厂方法,则应以不同的方式命名。你的export function pair 已经做得很好了——只需放弃of 方法。关于空,如果您的意思是表示列表的末尾 (Nil),您应该使用不是 Pair 实例的值 - 既可以是哨兵对象,也可以只是值 null

标签: javascript functional-programming


【解决方案1】:

SICP 是一篇精彩的文章。这里的代码应该是不言自明的。我目前时间紧,但我会在今天晚些时候跟进,以添加一些细节并回答潜在的后续问题 -


ma​​in.js

// main.js
import { of, fromArray, toString } from './list'

toString(of(999))
// 999->Empty.

toString(fromArray([]))
// Empty.

toString(fromArray([1]))
// 1->Empty.

toString(fromArray([1,2,3,4]))
// 1->2->3->4->Empty.

const big =
  Array.from(Array(100000), (_, x) => x)

toString(fromArray(big))
// 0->1->2->3->...99999->Empty.

list.js

import { loop, recur } from './function'

const empty =                      // <-- empty list
  Symbol()                         // <-- any sentinel value

const pair = (left, right) =>      // <-- pair constructor
  ({ pair, left, right })          // <-- plain object

const of = (x = null) =>           // <-- "of" constructor
  pair(x, empty)                   // <-- singleton list

const fromArray = (xs = []) =>
  loop                             // <-- begin loop
    ( ( r = empty                  // <-- init r
      , i = 0                      // <-- init i
      ) =>                         // <-- loop body
        i >= xs.length             // <-- exit condition
          ? reverse(r)             // <-- tail; return
          : recur                  // <-- tail; recur
              ( pair(xs[i], r)     // <-- next r
              , i + 1              // <-- next i
              )
    )

const reverse = (node = empty) =>
  loop                             // <-- begin loop
    ( ( r = empty                  // <-- init r
      , t = node                   // <-- init t
      ) =>                         // <-- loop body
        t === empty                // <-- exit condition
          ? r                      // <-- tail; return
          : recur                  // <-- tail; recur
              ( pair(t.left, r)    // <-- next r
              , t.right            // <-- next t
              )
    )

const toString = (node = empty) =>
  loop                             // <-- begin loop
    ( ( r = "Empty."               // <-- init r
      , t = reverse(node)          // <-- init t
      ) =>                         // <-- loop body
      t === empty                  // <-- exit condition
        ? r                        // <-- tail; return
        : recur                    // <-- tail; recur
            ( t.left + "->" + r    // <-- next r
            , t.right              // <-- next t
            )
    )

// "pair" is not exported
// it is an implementation detail of our list module
export { empty, of, fromArray, reverse, toString }

function.js

const identity = x => x

const recur = (...v) =>
  ({ recur, [Symbol.iterator]: _ => v.values() })

const loop = (f = identity, ...init) =>
  whileTrue               // <-- functional while
    ( r => r && r.recur   // <-- while condition
    , r => f(...r)        // <-- next r
    , f(...init)          // <-- init r
    )

const whileTrue = (test = identity, next = identity, r = null) =>
{ while(Boolean(test(r))) // <-- while test(r) is true
    r = next(r)           // <-- nexr r
  return r                // <-- return r
}

export { identity, loop, recur, whileTrue }

演示

展开下面的sn-p,在浏览器中验证结果-

// function.js -------
const identity = x => x

const recur = (...v) =>
  ({ recur, [Symbol.iterator]: _ => v.values() })

const loop = (f = identity, ...init) =>
  whileTrue
    ( r => r && r.recur
    , r => f(...r)
    , f(...init)
    )

const whileTrue = (test = identity, next = identity, r = null) =>
{ while(Boolean(test(r)))
    r = next(r)
  return r
}

// list.js -------
const empty =
  Symbol()

const pair = (left, right) =>
  ({ pair, left, right })

const of = (x = null) =>
  pair(x, empty)

const fromArray = (xs = []) =>
  loop
    ( ( r = empty
      , i = 0
      ) =>
        i >= xs.length
          ? reverse(r)
          : recur
              ( pair(xs[i], r)
              , i + 1
              )
    )

const reverse = (node = empty) =>
  loop
    ( ( r = empty
      , t = node
      ) =>
        t === empty
          ? r
          : recur
              ( pair(t.left, r)
              , t.right
              )
    )

const toString = (node = empty) =>
  loop
    ( ( r = "Empty."
      , t = reverse(node)
      ) =>
      t === empty
        ? r
        : recur
            ( t.left + "->" + r
            , t.right
            )
    )

// main.js -------
console.log(toString(of(999)))
// 999->Empty.

console.log(toString(fromArray([])))
// Empty.

console.log(toString(fromArray([1])))
// 1->Empty.

console.log(toString(fromArray([1,2,3,4])))
// 1->2->3->4->Empty.

const big =
  Array.from(Array(100000), (_, x) => x)

console.log(toString(fromArray(big)))
// 0->1->2->3->...99999->Empty.

进一步抽象

如果有更多时间,我可能会写 pair 作为它自己的模块 -

// pair.js
import { raise } from './function'

const empty =
  Symbol()

const pair = (left, right) =>
  ({ pair, left, right })

const left = (t = empty) =>
  t === empty
    ? raise(`cannot read value from empty`)
    : t.left

const right = (t = empty) =>
  t === empty
    ? raise(`cannot read value from empty`)
    : t.right

const of = ([ left, right ]) =>
  pair(left, right)

export { empty, pair, left, right, of }

raise 添加到function 模块-

// function.js
const identity = //

const recur = //

const loop = //

const whileTrue = //

const raise = (msg = "") => // functional throw
  { throw Error(msg) }

export { identity, loop, recur, whileTrue, raise }

list 模块和pair 模块之间创建更好的抽象屏障-

// list.js
import { loop, recur } from './function'
import { empty, pair, left, right } from './pair'

const of = (x = null) =>
  pair(x, empty)                   // <-- pair, empty

const fromArray = (xs = []) =>
  loop
    ( ( r = empty                  // <-- empty
      , i = 0
      ) =>
        i >= xs.length
          ? reverse(r)
          : recur
              ( pair(xs[i], r)     // <-- pair
              , i + 1              //
              )
    )

const reverse = (node = empty) =>
  loop
    ( ( r = empty                  // <-- empty
      , t = node
      ) =>
        t === empty                // <-- empty
          ? r
          : recur
              ( pair(left(t), r)   // <-- pair, left
              , right(t)           // <-- right
              )
    )

const toString = (node = empty) =>
  loop
    ( ( r = "Empty."
      , t = reverse(node)
      ) =>
      t === empty                  // <-- empty
        ? r
        : recur
            ( left(t) + "->" + r   // <-- left
            , right(t)             // <-- right
            )
    )

// re-export empty
// it's okay that List.empty and Pair.empty are
// represented using the same sentinel value
export { empty, of, fromArray, reverse, toString }

很高兴看到将pair 作为一个单独的步骤编写,因为它向我们展示了当数据结构变得过于复杂时如何分解模块。

【讨论】:

  • 感谢您的宝贵时间! “相当不言自明”:-)
  • 不客气,Ghita。我添加了更多内联 cmets 和一个新部分,其中包括一个单独的 pair 模块。
猜你喜欢
  • 2021-06-02
  • 2019-04-12
  • 1970-01-01
  • 2012-08-02
  • 2023-04-09
  • 2020-04-08
  • 2021-04-24
  • 2020-10-22
  • 2013-07-19
相关资源
最近更新 更多