我正在尝试理解以下zip函数(尤其是调用函数)如何更具功能性。我得到的问题是,invoke方法必须等待左侧和右侧都可以在它可以调度值之前填充。必须按顺序调用这些值,以便压缩正确的值,否则我会考虑使用curry / partial函数来实现此目的。
我能用什么来消除这种阻碍。
function zip(state, a, b) {
var left = [];
var right = [];
function invoke() {
if (left.length > 0 && right.length > 0) {
state([left.shift(), right.shift()]);
}
}
a.foreach(function(v) {
left.push(v);
invoke();
});
b.foreach(function(v) {
right.push(v);
invoke();
});
}
Bellow是满足zip功能的简单示例。
function Stream() {
var env = this;
env.subs = [];
env.id = setInterval(function() {
env.subs.forEach(function(f) {
f(Math.random());
});
}, ((Math.random() * 100) + 500) | 0);
}
Stream.prototype.foreach = function(f) {
this.subs.push(f);
}
zip(function(v) {
console.log(v);
}, new Stream(), new Stream());
奖励:删除可变数组。
答案 0 :(得分:5)
如果Stream
具有某种iterator
接口,将第一个元素中的列表与其后续元素分开(如Haskell列表已构建,您似乎知道),则可以使用更具功能性的方法它们)。
我知道这段代码起初比较复杂(至少更长),但使用结构会更方便:
function Promise(resolver) {
// you know better promise libs of course
// this one is not even monadic
var subs = [],
res = null;
resolver(function resolve() {
res = arguments;
while (subs.length) subs.shift().apply(null, res);
});
this.onData = function(f) {
if (res)
f.apply(null, res);
else
subs.push(f);
return this;
};
}
Promise.all = function() {
var ps = Array.prototype.concat.apply([], arguments);
return new Promise(function(resolve) {
var res = [],
l = ps.length;
ps.forEach(function(p, i) {
p.onData(function() {
while(res.length < arguments.length) res.push([]);
for (var j=0; j<arguments.length; j++)
res[j][i] = arguments[j];
if (--l == 0)
resolve.apply(null, res);
});
});
});
};
function Stream() {
// an asynchronous (random) list
var that = this,
interval = (Math.random() * 100 + 500) | 0;
this.first = new Promise(function create(resolve) {
that.id = setTimeout(function() {
resolve(Math.random(), new Promise(create));
}, interval);
});
}
// this is how to consume a stream:
Stream.prototype.forEach = function(f) {
this.first.onData(function fire(res, next) {
f(res);
next.onData(fire);
});
return this;
};
Stream.prototype.end = function() { clearTimeout(this.id); return this; };
但现在很容易压缩它们:
function zip() {
var res = Object.create(Stream.prototype); // inherit the Stream interface
res.first = (function create(firsts) {
return new Promise(function(resolve) {
Promise.all(firsts).onData(function(results, nexts) {
resolve(results, create(nexts));
});
});
})(Array.prototype.map.call(arguments, function(stream) {
return stream.first;
}));
return res;
}
zip(new Stream, new Stream).forEach(console.log.bind(console));
基本上我已经将你的第一个项目推广到Promise模式,其中Promise.all
具有并行等待功能,并且你的可变结果数组成为嵌套的promises列表。我通过使所有函数都可以使用任意数量的参数来避免代码重复(对于left
和right
)。
答案 1 :(得分:1)
我不明白你为什么要让你的代码更具功能性。尽管如此,我通过完全删除zip
函数改进了invoke
函数。你真的不需要它:
function zip(a, b, callback) {
var left = [], right = [];
a.forEach(function (value) {
if (right.length)
callback([value, right.shift()]);
else left.push(value);
});
b.forEach(function (value) {
if (left.length)
callback([left.shift(), value]);
else right.push(value);
});
}
自己查看输出:http://jsfiddle.net/Tw6K2/
代码比功能更为必要。但是我怀疑它比这更好。
答案 2 :(得分:0)
在对你的问题进行多思考之后,我相信我找到了一个更通用的解决方案。让我们从EventStream
构造函数(比Stream
构造函数更通用)开始:
function EventStream() {
this.listeners = [];
}
然后我们创建一个dispatch
方法来向流添加事件:
EventStream.prototype.dispatch = function (event) {
return this.listeners.map(function (listener) {
return listener(event);
});
};
接下来,我们将创建一个map
方法,该方法比foreach
方法更通用:
EventStream.prototype.map = function (f) {
var stream = new EventStream;
this.listeners.push(function (x) {
return stream.dispatch(f(x));
});
return stream;
};
现在,当您map
对事件流执行某项功能时,您将获得一个全新的事件流。例如,如果您的信息流为[0,1,3,5..]
,并且您将(+2)
映射到其上,则新信息流将为[2,3,5,7..]
。
我们还将创建一些更有用的实用方法,如filter
,scan
和merge
,如下所示:
EventStream.prototype.filter = function (f) {
var stream = new EventStream;
this.listeners.push(function (x) {
if (f(x)) return stream.dispatch(x);
});
return stream;
};
filter
方法过滤掉事件流中的某些事件,以创建全新的事件流。例如,给定[2,3,5,7..]
和函数odd
,过滤的事件流将为[3,5,7..]
。
EventStream.prototype.scan = function (a, f) {
var stream = new EventStream;
setTimeout(function () {
stream.dispatch(a);
});
this.listeners.push(function (x) {
return stream.dispatch(a = f(a, x));
});
return stream;
};
scan
方法用于累积创建新的事件流。例如,根据流[3,5,7..]
,初始值0
和扫描函数(+)
,新事件流将为[0,3,8,15..]
。
EventStream.prototype.merge = function (that) {
var stream = new EventStream;
this.listeners.push(function (x) {
return stream.dispatch(new Left(x));
});
this.listeners.push(function (y) {
return stream.dispatch(new Right(x));
});
return stream;
};
function Left(x) {
this.left = x;
}
function Right(x) {
this.right = x;
}
merge
方法将两个单独的事件流合并为一个。为了区分生成每个事件的流,我们将所有事件标记为左或右。
好吧,现在更大的问题。我们创建一个zip
方法。非常酷的是我们可以使用zip
,map
,filter
和scan
方法创建merge
,如下所示:
EventStream.prototype.zip = function (that) {
return this.merge(that).scan([[], [], null], function (acc, event) {
var left = acc[0], right = acc[1];
if (event instanceof Left) {
var value = event.left;
return right.length ?
[left, right.slice(1), new Just([value, right[0]])] :
[left.concat(value), right, null];
} else {
var value = event.right;
return left.length ?
[left.slice(1), right, new Just([left[0], value])] :
[tuple(left, right.concat(value), null];
}
})
.filter(function (a) {
return a[2] instanceof Just;
})
.map(function (a) {
return a[2].just;
});
};
function Just(x) {
this.just = x;
}
现在您可以按如下方式使用它:
stream1.zip(stream2).map(function (v) {
console.log(v);
});
您可以按如下方式定义stream1
和stream2
:
var stream1 = getRandomStream();
var stream2 = getRandomStream();
function getRandomStream() {
var stream = new EventStream;
setInterval(function () {
stream.dispatch(Math.random());
}, ((Math.random() * 100) + 500) | 0);
return stream;
}
这就是它的全部。不需要承诺。