sodium icon indicating copy to clipboard operation
sodium copied to clipboard

Multi input Stream transform?

Open rongcuid opened this issue 8 years ago • 15 comments

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 avatar Dec 09 '16 01:12 rongcuid

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?

rongcuid avatar Dec 09 '16 01:12 rongcuid

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.

the-real-blackh avatar Dec 09 '16 02:12 the-real-blackh

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?

rongcuid avatar Dec 09 '16 02:12 rongcuid

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?

the-real-blackh avatar Dec 09 '16 02:12 the-real-blackh

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.

rongcuid avatar Dec 09 '16 02:12 rongcuid

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.

the-real-blackh avatar Dec 09 '16 02:12 the-real-blackh

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

rongcuid avatar Dec 09 '16 03:12 rongcuid

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

rongcuid avatar Dec 09 '16 03:12 rongcuid

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!

the-real-blackh avatar Dec 09 '16 04:12 the-real-blackh

OK. It involves some Kleisli Arrow magic, though.

rongcuid avatar Dec 09 '16 04:12 rongcuid

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 [email protected] 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 .

FLUXparticle avatar Dec 09 '16 05:12 FLUXparticle

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 [email protected]mailto:[email protected] 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.

rongcuid avatar Dec 09 '16 06:12 rongcuid

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 [email protected] 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 [email protected]mailto:[email protected] 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 .

FLUXparticle avatar Dec 09 '16 06:12 FLUXparticle

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.

rongcuid avatar Dec 09 '16 06:12 rongcuid

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 avatar Dec 09 '16 16:12 jam40jeff

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

jam40jeff avatar Apr 22 '23 13:04 jam40jeff