我正在尝试重构此代码,该代码定义了一个unfold
函数,并使用它来制作count
函数,该函数将一个数组填充为最大数量的数字。我不想把count(100)
变成一个生成器,可以通过任意调用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
函数return [ value, ...unfold (fn, nextState)]
的第4行,但不确定如何将该收益率一直传递到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;
}
我想向您展示一个与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一样在没有括号的情况下调用一个空函数。它只需要一个普通的null函数并将其转换为隐式函数即可。这是我们更新的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]);