在调用之前按顺序完成所有函数参数

时间:2013-07-01 14:22:45

标签: javascript functional-programming frp

我正在尝试理解以下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());

奖励:删除可变数组。

3 个答案:

答案 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列表。我通过使所有函数都可以使用任意数量的参数来避免代码重复(对于leftright)。

答案 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..]

我们还将创建一些更有用的实用方法,如filterscanmerge,如下所示:

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方法。非常酷的是我们可以使用zipmapfilterscan方法创建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);
});

您可以按如下方式定义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;
}

这就是它的全部。不需要承诺。