Pergunta

I am trying to emulate event streams from the domain of functional reactive programming in JavaScript. There are fundamentally two ways I could do this:

  1. Give each event stream an array of listeners and allow external code to subscribe to events in the stream. I don't like this solution because it requires a lot of bookkeeping and mutable state.
  2. Reify lazy evaluation and thunks in JavaScript to simulate lazy lists, allowing you to manifest event streams as a lazy list of events generated by a conventional event listener. No bookkeeping.

Clearly the second method is far more desirable. Hence I assiduously wrote a few combinators to enable lazy evaluation in JavaScript:

function lazy(f, a) {
    return function (k) {
        return k(apply(f, a));
    };
}

function apply(f, a) {
    return f.apply(null, a);
}

The lazy combinator takes a function and a list of arguments and returns a thunk representing the return value of the function call.

function eval(a) {
    return typeof a === "function" ? a(id) : a;
}

function id(a) {
    return a;
}

The eval combinator either takes a thunk, evaluates it and returns its value or else returns an eager value unchanged. It allows you to treat both lazy and eager values the same.

Next I wrote a cons function to create a list:

function cons(head, tail) {
    return {
        head: head,
        tail: tail
    };
}

Then I wrote a few utility functions to put lazy evaluation to the test:

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))) :
    [];
}

Finally I got the first 10 Fibonacci numbers lazily as follows:

var xs = take(10, fib(-1, 1));

alert(JSON.stringify(toArray(xs))); // [0,1,1,2,3,5,8,13,21,34]

It worked like a charm: http://jsfiddle.net/Kc5P2/

So now I am attempting to create an event stream constructor in JavaScript using lazy lists. However because events are generated asynchronously I surmise that I would need to convert all my functions into continuation passing style.

The crux of the problem is that there's no way to defer the evaluation of a thunk if the value of the thunk is not yet available. The solution is to create an asynchronous eval function as follows:

function asyncEval(a, k) {
    typeof a === "function" ? a(k) : k(a);
}

Now you can return an asynchronous thunk from the event stream constructor as follows:

function getEventStream(type, target) {
    return function (k) {
        target.addEventListener(type, function listener(event) {
            target.removeEventListener(type, listener);
            k(cons(event, getEventStream(type, target)));
        });
    };
}

Unfortunately now that we've made it asynchronous we'll need to create asynchronous versions of all the functions that operate on lazy lists as well (e.g. take). Not only are we now stuck in a callback hell but we also need two versions of each function: synchronous and asynchronous.

How would you resolve this problem? Is there any other way to create lazy event streams in JavaScript? BTW I already created a gist for event streams in JavaScript using the subscription model: https://gist.github.com/aaditmshah/8381875

Foi útil?

Solução

I'd go for promises, which are perfect for representing both synchronously and asynchronously available values.

A stream would then be a promise for a structure that contains the head, and a promise for the tail. You might want to have a look at my demo implementation at https://stackoverflow.com/a/17414193/1048572.

Outras dicas

I was doing some research into streams in JavaScript last year:

What's the relationship between streamjs and linqjs

I ended up reverse engineering streamjs and extending it for my purposes.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top