Я хочу показать вам реализацию, максимально приближенную к исходной реализации развертывания в 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
, которая вводит неявный thunk в Javascript, то есть нулевую функцию, которую можно вызывать без скобок, как ленивый метод получения на объекте. Он просто берет нормальную нулевую функцию и преобразует ее в неявную. Вот наш обновленный unfoldr
:
unfoldr = f => state => {
const go = ([x, state_]) =>
state_ === undefined
? []
: arrCons(x) (thunk(() => go(f(state_))));
return go(f(state));
};
Теперь, когда мы mimi c нестрогая оценка, выражение в рекурсивном шаге вычисляется достаточно просто, то есть приводится к форме [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]);