SodiumFRP / sodium

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

Multi input Stream transform? #107

Closed rongcuid closed 1 year ago

rongcuid commented 7 years ago

Say you have two Stream of different type of values that are guaranteed to arrive at the same time(because they are generated by a single Stream before), and a function (a,b) -> c to process them. Also known is that a, b, c do not have sensible "default values", so holding the streams don't make sense.

Currently I don't see a way to do this. There is only a merge, which can process Stream with inputs/outputs of the same type. There is also lift, but that works for Cells.

I am just experimenting with sodium so I am not sure if Streams are the correct way for this task. Yes, these streams can be held if Optional is used, which gives a default of None, but that would complicate the processing function signature to (Optional[A], Optional[B]) -> Optional[C]. Am I missing anything, or is Sodium missing this generalized merge?

rongcuid commented 7 years ago

A more detailed thought experiment shows that such a merge is impossible mathematically... If we account for the case where only one Stream fires. Then I wonder what should be done in this situation? Try to put the values into Cells?

the-real-blackh commented 7 years ago

Assuming two events are simultaneous is generally discouraged, but I have encountered use cases for it in real applications.

The way to do it is to write your own function to do it based on merge, map and filter. It must first turn the two streams into the same type. This can be done as a variant type that can be either A, B or C (however that's done in your target language). Then you merge the results, and in the function for the simultaneous case, you can simply assume in the function that the left value is always type A, the right is always type B, and you output a value of type C. After that, you can filter out values other than C. If you want to handle the non-simultaneous case, you can do that however you like.

Of course a lot of it depends on what you are trying to do!

I didn't add this as a primitive, because it's easy to write it yourself, and making Sodium understandable and not confusing is a far more important goal in the design than making it have all the helpers that you might want.

rongcuid commented 7 years ago

I see. So what would be the way to prevent the assumption of simultaneous events? Say, snapshot the original single Stream into a cell of Optional value, and use lift and map on the optional values?

the-real-blackh commented 7 years ago

Well, I didn't say that assuming two events are simultaneous is wrong necessarily, so that might be the best way.

Can you tell me a little bit more about what you're doing?

rongcuid commented 7 years ago

I am testing on a text game, where the command, which is discrete event, is parsed into two sections -- Verb and Object. Later, the Verb and Object combination will be checked for validity based on some current state.

Here, I think I can assume that Verb and Object must arrive simultaneously, since the pure parser should finish in one step.

the-real-blackh commented 7 years ago

I see. Well, at first I thought maybe that's not a suitable problem for FRP. But, I can see how the state management of FRP could be useful for representing the game world - including non-player characters.

It seems to me that the merge operation I described would work great in this situation. You can write this as a generalized operation and just use it wherever you need to in the code.

It seems like a reasonable assumption that if there's a verb, then there would also be an object. You can even have different ways in which the identification of objects is done, and select your object from a merge of a number of them, and then deal also with the case where no matching object was found as a non-simultaneous case.

rongcuid commented 7 years ago

I see. I will try both and see which one produces "better" code.

rongcuid commented 7 years ago

I found a reasonable solution. I am actually not working in Sodium, but developing my own FRP library, which has virtually identical primitives, and takes advantage of the Haskell's Arrow syntax. Actually, I think it makes sense that assuming events arrive simultaneously smells a bit because you cannot directly get this assumption when looking at the FRP network. What I like the most is to compose the individual parser using the function arrow, which guarantees synchronous computation, and you can read this from the type. BTW, it makes two functions a->b and a'->b' into (a,a') -> (b,b'), and it is guaranteed that no delay would ever be introduced inside the network, since the function Arrow is pure.

I hope Java has Arrow syntax too, but that is specific to haskell, so when using Sodium it may involve some deep tuples, and probably ad-hoc functions zip2, zip3, zip4, .... And the input port would be a single tuple.

If you are interested, https://github.com/carldong/timeless

the-real-blackh commented 7 years ago

I have been down this route, but I can't give you any code (because it was proprietary and not owned by me). It turned out to be really complicated. So, it'll be interesting to see how it comes out!

rongcuid commented 7 years ago

OK. It involves some Kleisli Arrow magic, though.

FLUXparticle commented 7 years ago

I would just ommit splitting up the values in the first place... Just let your parser return an object with verb and object in it... So you don't have to merge them again.

Rongcui Dong notifications@github.com schrieb am Fr., 9. Dez. 2016, 05:14:

OK. It involves some Kleisli Arrow magic, though.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/SodiumFRP/sodium/issues/107#issuecomment-265929448, or mute the thread https://github.com/notifications/unsubscribe-auth/AP0dG9dbLDmzAdeIMkCaWQksHxfo2UIPks5rGNWKgaJpZM4LIflk .

rongcuid commented 7 years ago

I am pretty sure that it will square my parser size, right?

On 12/08/2016 09:58 PM, Sven Reinck wrote: I would just ommit splitting up the values in the first place... Just let your parser return an object with verb and object in it... So you don't have to merge them again.

Rongcui Dong notifications@github.commailto:notifications@github.com schrieb am Fr., 9. Dez. 2016, 05:14:

OK. It involves some Kleisli Arrow magic, though.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/SodiumFRP/sodium/issues/107#issuecomment-265929448https://github.com/SodiumFRP/sodium/issues/107#issuecomment-265929448, or mute the thread https://github.com/notifications/unsubscribe-auth/AP0dG9dbLDmzAdeIMkCaWQksHxfo2UIPks5rGNWKgaJpZM4LIflkhttps://github.com/notifications/unsubscribe-auth/AP0dG9dbLDmzAdeIMkCaWQksHxfo2UIPks5rGNWKgaJpZM4LIflk .

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHubhttps://github.com/SodiumFRP/sodium/issues/107#issuecomment-265940170, or mute the threadhttps://github.com/notifications/unsubscribe-auth/ABQHDdxkuu4x6CKnRQ8nG3ZjII5qtw_4ks5rGO3ugaJpZM4LIflk.

FLUXparticle commented 7 years ago

I don't know how your parser works... But I know a thing or two about parsers ;-) So if you like, we can talk about your parser directly.

Rongcui Dong notifications@github.com schrieb am Fr., 9. Dez. 2016, 07:14:

I am pretty sure that it will square my parser size, right?

On 12/08/2016 09:58 PM, Sven Reinck wrote: I would just ommit splitting up the values in the first place... Just let your parser return an object with verb and object in it... So you don't have to merge them again.

Rongcui Dong notifications@github.commailto:notifications@github.com schrieb am Fr., 9. Dez. 2016, 05:14:

OK. It involves some Kleisli Arrow magic, though.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/SodiumFRP/sodium/issues/107#issuecomment-265929448< https://github.com/SodiumFRP/sodium/issues/107#issuecomment-265929448>, or mute the thread < https://github.com/notifications/unsubscribe-auth/AP0dG9dbLDmzAdeIMkCaWQksHxfo2UIPks5rGNWKgaJpZM4LIflk < https://github.com/notifications/unsubscribe-auth/AP0dG9dbLDmzAdeIMkCaWQksHxfo2UIPks5rGNWKgaJpZM4LIflk>

.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub< https://github.com/SodiumFRP/sodium/issues/107#issuecomment-265940170>, or mute the thread< https://github.com/notifications/unsubscribe-auth/ABQHDdxkuu4x6CKnRQ8nG3ZjII5qtw_4ks5rGO3ugaJpZM4LIflk>.

— You are receiving this because you commented.

Reply to this email directly, view it on GitHub https://github.com/SodiumFRP/sodium/issues/107#issuecomment-265941922, or mute the thread https://github.com/notifications/unsubscribe-auth/AP0dGws0UH880eXJMHP0FZIw9Iwl2FbFks5rGPG-gaJpZM4LIflk .

rongcuid commented 7 years ago

The point is not about this specific parser, but compositionality. If I have two pure functions that work on two different parts of a stream, there better be some way to combine them without multiplying into a larger one. In this example, when I make it a larger parser, I lose all the intermediate values because they won't be visible in the FRP network.

jam40jeff commented 7 years ago

If Verb and Object must arrive simultaneously, then that means you MUST be parsing out both a Verb and Object at the same time. Why can't you do something like:

stream.Map(streamData => new VerbAndObject(ParseVerb(streamData), ParseObject(streamData)));

(Sorry, I just realized you're working in Haskell and I provided a C# example, but hopefully it still makes sense.)

jam40jeff commented 1 year ago

Closing due to inactivity. Also, I believe that keeping a stream with both results in one object is the solution here.