首页 > 解决方案 > Javascript ES6:为展开函数实现生成器

问题描述

我正在尝试重构此代码,该代码定义了一个unfold函数并使用它来制作该count函数,该函数用最多计数的数字填充数组。我不想调用,而是count(100)想将 count 变成一个可以通过next()任意调用来使用的生成器。

function unfold (fn, state) {
    return fn( 
        (value, nextState) => {
            return [ value, ...unfold (fn, nextState)]
        },
        ()=>[],
        state
    );
}

function count (max) {
    return unfold(
        (next, done, state)=>{
            return state >= max ?
            done() :
            next(state, state +1)
        }, 
        0
    );
}

这里的流程已经有点难以理解,我很难弄清楚 yield 语句的流程应该如何工作。我想产生结果数组,它是unfold函数的第 4 行,return [ value, ...unfold (fn, nextState)]但我不确定如何将它一直传递给 count 函数。

这是我到目前为止所拥有的,但它只是返回一个带有生成器的生成器,然后在几次next调用后结束:

function * _unfold (fn, base) {
    yield * fn(
        (value, nextState)=>([ value, ..._unfold (fn, nextState)]),
        base
    )

    return [];
}

function * count (max) {

    yield * _unfold(
        compress,
        0
    );
    return 0;

}

function * compress (next, state) {
    yield next(state, state +1)
    return null;
}

标签: javascriptecmascript-6functional-programminggenerator

解决方案


我想向您展示一个尽可能接近 FP 中的原始展开实现的实现。希望从那里您可以使用命令式生成器来实现它。

这是第一个版本unfoldr

unfoldr = f => state => {
  const go = ([x, state_]) =>
    state_ === undefined
      ? []
      : arrCons(x) (go(f(state_)));
//                  ^^^^^^^^^^^^^ strictly evaluated

  return go(f(state));
};

展开是一个本质上是无限的过程,因此你需要惰性来阻止它。更准确地说,您需要一个构建结构的函数,该结构的第二个参数是非严格的。arrCons在这两个参数中都可以是非严格的,因为它所做的只是将它们存储在类似对的数据类型中。但是,Javascript 是经过严格评估的。

假设我们有一个thunk向 Javascript 引入隐式 thunk 的函数,即一个可以在没有括号的情况下调用的空函数,就像对象上的惰性 getter。它只需要一个普通的空函数并将其转换为隐式函数。这是我们的更新unfoldr

unfoldr = f => state => {
  const go = ([x, state_]) =>
    state_ === undefined
      ? []
      : arrCons(x) (thunk(() => go(f(state_))));

  return go(f(state));
};

现在我们模拟了非严格评估,递归步骤中的表达式被评估得恰到好处,即简化为形式[x, Thunk]

仅此而已。请注意,我们用它[]来表示基本情况,从而表示展开过程的结束。我们宁愿用标记的联合来编码这种行为,即Option/Maybe类型。但为了简单起见,我将实现保持原样。

unfoldr以下是定义斐波那契数列如何使用的示例:

const arrCons = head => tail =>
  [head, tail];

const unfoldr = f => state => {
  const go = ([x, state_]) =>
    state_ === undefined
      ? []
      : arrCons(x) (thunk(() => go(f(state_))));

  return go(f(state));
};

const fibs = unfoldr(
  ([x, y]) => [x, [y, x + y]]) ([0, 1]);

const main = fibs[1] [1] [1] [1] [1] [1] [1] [1] [1] [1]; // [55, Thunk]

main[0]; // 55

这是thunk返回 a的完整实现Proxy

const thunk = f =>
  new Proxy(f, new ThunkProxy(f));

const THUNK = "scriptum_thunk";

class ThunkProxy {
  constructor(f) {
    this.memo = undefined;
  }

  apply(g, that, args) {
    if (this.memo === undefined)
      this.memo = g();

    return this.memo(...args);
  }

  defineProperty(g, k, descriptor) { debugger;
    if (this.memo === undefined)
      this.memo = g();

    Object.defineProperty(this.memo, k, descriptor);
    return true;
  }

  get(g, k) {
    if (this.memo === undefined)
      this.memo = g();

    if (k === THUNK)
      return true;

    else if (k === Symbol.toPrimitive)
      return () => this.memo;

    else if (k === "valueOf")
      return () => this.memo;

    else return this.memo[k];
  }

  has(g, k) {
    if (this.memo === undefined)
      this.memo = g();

    return k in this.memo;
  }

  set(g, k, v) {
    if (this.memo === undefined)
      this.memo = g();

    this.memo[k] = v;
    return true;
  }  
}

const arrCons = head => tail =>
  [head, tail];

const arrUnfoldr = f => state => {
  const go = ([x, state_]) =>
    state_ === undefined
      ? []
      : arrCons(x) (thunk(() => go(f(state_))));

  return go(f(state));
};

const fibs = arrUnfoldr(
  ([x, y]) => [x, [y, x + y]]) ([0, 1]);

const main = fibs[1] [1] [1] [1] [1] [1] [1] [1] [1] [1]; // [55, Thunk]

console.log(main[0]);


推荐阅读