我想向您展示一个尽可能接近 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 返回一个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]);