【发布时间】:2014-02-02 17:48:22
【问题描述】:
我正在尝试在 JavaScript 中模拟功能反应式编程领域的事件流。基本上有两种方法可以做到这一点:
- 为每个事件流提供一个
listeners数组,并允许外部代码订阅流中的事件。我不喜欢这个解决方案,因为它需要大量的簿记和可变状态。 - 在 JavaScript 中实现惰性求值和 thunk 以模拟惰性列表,允许您将事件流显示为由常规事件侦听器生成的惰性事件列表。不记账。
显然第二种方法更可取。因此,我刻苦地编写了一些组合子来在 JavaScript 中启用惰性求值:
function lazy(f, a) {
return function (k) {
return k(apply(f, a));
};
}
function apply(f, a) {
return f.apply(null, a);
}
lazy 组合器接受一个函数和一个参数列表,并返回一个代表函数调用返回值的 thunk。
function eval(a) {
return typeof a === "function" ? a(id) : a;
}
function id(a) {
return a;
}
eval 组合器要么接受一个 thunk,评估它并返回它的值,要么返回一个不变的急切值。它允许您对惰性值和渴望值一视同仁。
接下来我写了一个cons函数来创建一个列表:
function cons(head, tail) {
return {
head: head,
tail: tail
};
}
然后我写了一些实用函数来测试惰性求值:
function fib(a, b) {
var c = a + b;
return cons(c, lazy(fib, [b, c]));
}
function take(n, xs) {
return xs && n > 0 ?
cons(xs.head, lazy(take, [n - 1, eval(xs.tail)])) :
null;
}
function toArray(xs) {
return xs ?
[xs.head].concat(toArray(eval(xs.tail))) :
[];
}
最后我懒惰地得到了前10个斐波那契数如下:
var xs = take(10, fib(-1, 1));
alert(JSON.stringify(toArray(xs))); // [0,1,1,2,3,5,8,13,21,34]
它就像一个魅力:http://jsfiddle.net/Kc5P2/
所以现在我尝试使用惰性列表在 JavaScript 中创建事件流构造函数。然而,由于事件是异步生成的,我推测我需要将所有函数转换为延续传递样式。
问题的关键在于,如果 thunk 的值尚不可用,则无法推迟对 thunk 的评估。解决方法是创建一个异步的eval函数如下:
function asyncEval(a, k) {
typeof a === "function" ? a(k) : k(a);
}
现在您可以从事件流构造函数返回一个异步 thunk,如下所示:
function getEventStream(type, target) {
return function (k) {
target.addEventListener(type, function listener(event) {
target.removeEventListener(type, listener);
k(cons(event, getEventStream(type, target)));
});
};
}
不幸的是,现在我们已经使它异步了,我们还需要为所有在惰性列表上运行的函数创建异步版本(例如take)。我们现在不仅陷入了回调地狱,而且我们还需要每个函数的两个版本:同步和异步。
你会如何解决这个问题?有没有其他方法可以在 JavaScript 中创建惰性事件流?顺便说一句,我已经使用订阅模型为 JavaScript 中的事件流创建了一个要点:https://gist.github.com/aaditmshah/8381875
【问题讨论】:
标签: javascript functional-programming lazy-evaluation reactive-programming