reverofevil / es7-effect

Experiments for ES7 proposal to add monads instead of async/await which is essentially a continuation monad.
1 stars 0 forks source link

Similar work #1

Open pelotom opened 8 years ago

pelotom commented 8 years ago

Hi, this isn't really an issue, just wanted to send you a note...

I came across your README as I was looking into how to implement do-notation using JavaScript generators, and struggling with the mutable generator problem you talk about there. It was quite a pleasant surprise to see you mention my Effectful library in your writeup :) I thought you might be interested to know the solution I arrived at, which was to emulate immutable generators by maintaining a history of input values and replaying them as needed to create generator clones. I put that code into this library:

https://github.com/pelotom/immutagen

Here's my do-notation library, which looks like it has some similar aspirations to yours:

https://github.com/pelotom/burrido

Cheers!

reverofevil commented 8 years ago

Not as pleasant as to see an email from the author of one of your favourite libraries :)

README doesn't mention it all. Here's the full story. I was experimenting with immutable generators too, but I've thought that it would be useless without effects. The main use case was to allow user not just scrap some callbacks, but EventEmitters too. (Then I found some related work.) Usually event handlers change DOM state, and nobody wants mutations to be replayed.

Semantics were not obvious, so I had to model it in Haskell. The model was that we have a monad transformer stack of StateT for variables, ErrorT for exceptions and ReacT for things like setTimeout, with JS-defined monad inside. That helped a lot.

But the real problem was that to make this work properly I had to create a translation pass that converted local variables into immutable.js primitives. And then there were prototypes, graph-like data structures with cyclic references (that required tying the knots) and a whole range of other problems that made me reconsider if I really wanted to solve that problem. So I gave up.

I still have a "plan B": use generators with replay and come up with wrappers around DOM that won't suffer of replays. burrido looks very handy for this.

Great job!

pelotom commented 8 years ago

Re "useless without effects": you should never need to have any effects that are not "encapsulated" by the monad, so the only problem incurred by replaying should be one of performance. This is exactly how it works in Haskell, only there the type system enforces that you have no unencapsulated side effects. You could even define an IO monad using burrido, although just like in Scala it's probably more trouble than it's worth.

I'm not sure about what you want to do with EventEmitter, but my driving use case for making burrido was to be able to define various monads for RxJS Observables and use do-notation with them. Then you can subscribe to a resulting Observable and do DOM manipulations based on it. A better technique as espoused by CycleJS is to transform streams of events into streams of new DOMs which can then be virtually diffed and merged.

reverofevil commented 8 years ago

Sure, that's what I've called "wrappers around DOM that won't suffer of replays". I'm concerned that popular libraries don't know that they cannot mutate DOM any time they want, and they have to be thoroughly rewritten. I don't think it's possible to put React.render as yet another primitive of IO and expect it to work properly.

I'm not sure about what you want to do with EventEmitter

EventEmitter is just a node.js-specific example. There are one-shot continuations (promises, call/1cc) and proper continuations (EventEmitters, call/cc). Proper continuations require replays of iteration history. They are more interesting, because you can do

// stream of 0, 1, 2, ...
const a = iota;

// with 1s intervals 
const b = dilute(a);

// with result accumulation
const scan = (x, f) => FRP.Do(function* () {
    let s = 0;
    s = f(s, yield x);
    return s;
});
const c = scan(b, (x, y) => x + y);

// 0\n1\n3\n... with 1s intervals
c.on('data', x => console.log(x));

or even

// 1-to-1 translation of sequence diagram for several asynchoronous agents
const workflow = Agents.Do(function* () {
    yield User.register();
    yield async.parallel(
        MailDaemon.send(...),
        Agents.Do(function* () {
            const url = yield User.visitPage();
            yield Server.respond(... url ...);
        });
    );
});

that you'd have to write with lots of EventEmitters otherwise.