1

我试图了解如何使以下 zip 函数(尤其是调用函数)更具功能性。我遇到的问题是调用方法必须等待左侧和右侧都被填充,然后才能分派值。必须按顺序调用这些值,以便压缩正确的值,否则我会考虑使用 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());

奖励:删除可变数组。

4

3 回答 3

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具有并行等待的特性,并将您的可变结果数组概括为嵌套的承诺列表。而且我通过使所有函数使用任意数量的参数来避免代码重复(forleft和)。right

于 2013-07-01T21:41:46.273 回答
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/

代码比功能更重要。但是我怀疑它会比这更好。

于 2013-07-01T15:56:34.580 回答
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如下所示:scanmerge

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,如下所示:scanmerge

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);
});

您可以定义stream1stream2如下:

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;
}

这里的所有都是它的。不需要承诺。

于 2014-02-03T04:12:41.767 回答