SodiumFRP / sodium

Sodium - Functional Reactive Programming (FRP) Library for multiple languages
http://sodium.nz/
Other
848 stars 138 forks source link

C# Stream.Merge isn't triggered #98

Closed RobertWalter83 closed 8 years ago

RobertWalter83 commented 8 years ago

Hi all, I have the following stream setup in a project:

StreamSink<KeyEventArgs> sKeys = new StreamSink<KeyEventArgs>();
this.KeyDown += (sender, args) => sKeys.Send(args);
this.KeyUp += (sender, args) => sKeys.Send(args);
Stream<KeyEventArgs> sUp = sKeys.Filter(args => args.Key == Key.Up);
Stream<KeyEventArgs> sDown = sKeys.Filter(args => args.Key == Key.Down);

Stream<int> sYDec = sUp.Map(args => args.IsDown ? -1 : 0);
Stream<int> sYInc = sDown.Map(args => args.IsDown ? 1 : 0);
Stream<int> sY = sYDec.Merge(sYInc, (dec, inc) => dec + inc);
...

My expectation is that sY is a stream of the result of (dec, inc) => dec + inc and that this is fired everytime either sYDec or sYInc fires. Maybe I got the semantics wrong, but my function (dec, inc) => dec + inc is never executed. Instead, sY has the value of sYDec OR sYInc respectively (depending on which key was pressed last.

Any ideas?

RobertWalter83 commented 8 years ago

My current workaround to this looks like this, btw.:

StreamSink<KeyEventArgs> sKeys = new StreamSink<KeyEventArgs>();
this.KeyDown += (sender, args) => sKeys.Send(args);
this.KeyUp += (sender, args) => sKeys.Send(args);
Stream<KeyEventArgs> sUp = sKeys.Filter(args => args.Key == Key.Up);
Stream<KeyEventArgs> sDown = sKeys.Filter(args => args.Key == Key.Down);

Cell<int> cYDec = sUp.Map(args => args.IsDown ? -1 : 0).Calm().Hold(0);
Cell<int> cYInc = sDown.Map(args => args.IsDown ? 1 : 0).Calm().Hold(0);
Cell<int> cY = cYDec.Lift(cYInc, (dec, inc) => dec + inc);
the-real-blackh commented 8 years ago

It's working as intended. The function passed to Merge is only used in the case where sYDec and sYInc output a value simultaneously, that is, both have a value within the same transaction. In your code this would require doing two Sends within an explicit transaction.

Stream processing is stateless without cells. If you need to keep a value from one transaction to the next, there had to be a Hold somewhere.

If you can't figure out how to do what you want, you could use it as an excuse to try out the forum (http://sodium.nz/) I'll be happy to help out.

the-real-blackh commented 8 years ago

Oh, I think I see what the intent is. The workaround code is correct but if you need the output to be a stream, then you'll need to use Snapshot twice and merge the results. Using Operational.Updates() on cY is equivalent but shorter but there are potential philosophical implications which I expound upon at length in the book and also describe in the API docs. In this particular situation you could make an argument that this is the right thing to do.

RobertWalter83 commented 8 years ago

Thanks Steven, I understand the intention of Merge now better. I think I'll go with my workaround for now, since what I need eventually is a Cell<Tuple<int, int>>.