-
Notifications
You must be signed in to change notification settings - Fork 71
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add initial Event to switchE #165
Conversation
Is there anything I can do to help move this along? |
@HeinrichApfelmus, any thoughts on this one? |
lp <- stepperL never pp | ||
switchP :: Pulse a -> Pulse (Pulse a) -> Build (Pulse a) | ||
switchP p pp = mdo | ||
lp <- stepperL p pp |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before, the latch-of-pulse had an initial pulse never
. Now, p
.
p1 <- newPulse "switchP_in" switch :: Build (Pulse ()) | ||
p1 `dependOn` pp | ||
p2 <- newPulse "switchP_out" eval | ||
p2 `dependOn` p |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before, p2
was initialized with no parents, and would not have a parent until pp
fired. p1
, a child of pp
, would switch p2
's parent to the newly fired event.
Now, p2
does have an initial parent - p
. This, like before, will get switched away to a new parent by p1
when pp
fires.
In addition to the use case I mentioned in #162, I think I've hit another. In brief: I have a "game session" that's modeled as: gameSessionMoment :: Event Input -> Moment (Behavior GameState, Event ()) where the output I thought about turning this into an infinite game, by simply restarting the whole thing when the player dies. This would mean the final game creation function becomes gameMoment :: Event Input -> Moment (Behavior GameState) (with some out-of-band mechanism for quitting the game, like pressing I got stuck writing the second function in terms of the first. Here's an attempt: gameMoment :: Event Input -> Moment (Behavior GameState)
gameMoment eInput = do
(bFirstGameState, eFirstGameOver) <- gameSessionMoment eInput
-- Event that fires when the current game is over
let eGameOver = undefined :: Event ()
let eNewGameSession :: Event (Behavior GameState, Event ())
eNewGameSession = observeE (gameSessionMoment eInput <$ eGameOver)
switchB bFirstGameState (fst <$> eNewGameSession) In English:
However I could not figure out how to define eGameOver <- switchE eFirstGameOver (snd <$> eNewGameSession) |
Hi, will this ever be merged? I found a case where I think something like this is needed, namely when trying to create a type akin to My attempt would look approximately like this data Dynamic a = Dynamic { dynBehavior :: Behavior a, dynEvent :: Event a }
deriving Functor
joinDyn :: forall m a. MonadMoment m => Dynamic (Dynamic a) -> m (Dynamic a)
joinDyn (Dynamic behDyn evDyn) = do
d1 <- valueB behDyn
e <- switchE (dynEvent d1) (dynEvent <$> evDyn)
b <- switchB (dynBehavior d1) (dynBehavior <$> evDyn)
return $ Dynamic b e which I think should yield the correct semantics, though I'm actually quite new to FRP in general. |
I'm not sure. Despite having written the patch, I now forget most of what I (thought I) knew about the internals, and it's tough to say without an expert reviewer whether this patch could be considered correct (is there a space leak now? etc). Perhaps once I have more time I'll continue my side-project of stripping down and refactoring |
To move this forward, I'd like to look at a standalone example of where we really really need this change. It's hard to be sufficiently motivated with where things stand at the moment. This isn't to say "let's not do this", but more "let's make sure we really understand why we're doing this". The game example you provide @mitchellwrosen looks like a great start here, so maybe we should turn that into something we can run and try and find some solutions there. I understand @Invisible-Rabbit-Hunter's suggestion about |
Actually, I'm coming around to this. In #162, @HeinrichApfelmus acknowledges that he thinks this is probably something new. In #74 (comment), @ChristopherKing42 shows that So maybe we do have sufficient evidence to move on this. @HeinrichApfelmus, any thoughts? |
It may also be worth contrasting other FRP systems. Notably I looked at switchHold :: (Reflex t, MonadHold t m) => Event t a -> Event t (Event t a) -> m (Event t a) which is precisely the new switchHold ea0 eea = switch <$> hold ea0 eea where switch :: Behavior t (Event t a) -> Event t a
hold :: a -> Event t a -> m (Behavior t a) are primitive (at least in the sense they are methods of Eitherway, I would probably still want to provide users with |
|
@mitchellwrosen I've merged |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm on board with this, so I'm going this my approval. Tagging in @HeinrichApfelmus for the next review!
(Sorry for the long delay on this.) I think that I'm onboard with the change, especially as @PaulJohnson has given an implementation in terms of existing combinators, #162 (comment) , but the new version does have better garbage collection (as I main question I have is: Do we want to change the type of |
A tricky question! A quick search on Hackage search suggests that there is no Hackage usage of If it were me, I'd be inclined to make a breaking change. |
-- > switchE ee = \time0 -> concat [trim t1 t2 e | (t1,t2,e) <- intervals ee, time0 <= t1] | ||
-- > switchE e0 ee = \time0 -> [(t, a) | (t,a) <- e0, t < t1] ++ concat [trim t1 t2 e | (t1,t2,e) <- intervals ee, time0 <= t1] | ||
-- > where | ||
-- > t1 = head [t | (t, _) <- ee] | ||
-- > intervals e = [(time1, time2, x) | ((time1,x),(time2,_)) <- zip e (tail e)] | ||
-- > trim time1 time2 e = [x | (timex,x) <- e, time1 < timex, timex <= time2] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Neither is correct, I believe, not the least because t1
is not in scope. 😅 The correct semantics is
switchE e0 ee0 time0 =
concat [ trim t1 t2 e | (t1,t2,e) <- intervals ee ]
where
laterThan e time0 = [(timex,x) | (timex,x) <- e, time0 < timex ]
ee = [(time0, e0)] ++ (ee0 `laterThan` time0)
intervals ee = [(time1, time2, e) | ((time1,e),(time2,_)) <- zip ee (tail ee)]
trim time1 time2 e = [x | (timex,x) <- e, time1 < timex, timex <= time2]
Formulated in this way, it becomes clearer how e
aligns with the occurrences in ee
, and the trim
function indicates that <=
instead of <
is the right choice.
The other main question is: What happens right at time0
? If the ee
event has an occurrence at this moment, then this will be ignored — that's why we need the initial event e
in the first place. Any occurrence of e
at this moment will also be ignored, however, also by virtue of trim
.
(That said, the semantics was never quite correct in the case where ee
is a singleton or the empty list. 🙈 But we can exclude this corner case by proclaiming that the semantics are only supposed to hold in the other cases.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot for the pull request, @mitchellwrosen ! 😊
If somebody could
- Change the comment for
switchE
to use the correct semantics - Add a major version bump to the
.cabal
and update the changelog
then I think we're ready to merge!
@HeinrichApfelmus Done! |
This patch changes the type of
switchE
fromto
as briefly discussed in #162
Consider this a second attempt at a feature request, this time with code attached :)
I'm not fully convinced this change is necessary, as I'm quite new to FRP in general, but this is a pattern that seems to exist in other frameworks. Happy to hear reasons why this is not needed, or if it is, I hope I've implemented it properly! Thanks.