[reactive] Event's Monad instance and joinE

1 view
Skip to first unread message

Chris Lesniewski-Laas

unread,
Jul 10, 2009, 10:39:16 AM7/10/09
to reac...@haskell.org
Hi. I've been experimenting with using Reactive as the basis of a network
daemon I'm working on, with the top-level type Event Packet -> Event Packet.

I've run into a difficult-to-debug strictness problem with the Monad instance
for EventG, at least when used together with makeEvent. Here's a minimal test
program that shows the problem:

> import FRP.Reactive
> import FRP.Reactive.LegacyAdapters
> import Control.Concurrent
> import Control.Monad
>
> my_event :: IO (Event Char)
> my_event = do
> (char_sink, char_event) <- makeEvent =<< makeClock
> forkIO $ forever $ char_sink =<< getChar
> return char_event
>
> main = do
> chars <- my_event
> adaptE $ do c <- chars ; return (print c)

Running this program consumes 100% of CPU and doesn't respond to input.
On the other hand, replacing the last line with

> adaptE $ print `fmap` chars

does what you'd expect (prints each character read from stdin as it is
received).

I'm pretty sure what's happening here is adaptE evaluating joinE, which is in
turn evaluating its recursive call to joinE without being properly lazy and
waiting for the next occurrence of Event (Event Char), leading to an infinite
loop.

I've played around with the definition of joinE, but I don't have a
sufficiently good mental model of the evaluator to figure out what needs to
become stricter or lazier to fix this problem. In any case, my changes have
either resulted in stack overflows or evaluating mempty.

Perhaps I am misunderstanding the contract for Event's Monad instance.
Is there a reason why it shouldn't be used with makeEvent or even adaptE?
mkUpdater would be a poor substitute for adaptE, since a network daemon doesn't
have any obvious equivalent to a graphical program's idle loop; ideally the
output should be driven by incoming packets.

Thanks,
Chris
_______________________________________________
Reactive mailing list
Reac...@haskell.org
http://www.haskell.org/mailman/listinfo/reactive

Svein Ove Aas

unread,
Jul 10, 2009, 10:47:49 AM7/10/09
to Chris Lesniewski-Laas, reac...@haskell.org
On Fri, Jul 10, 2009 at 4:39 PM, Chris Lesniewski-Laas<c...@mit.edu> wrote:
> Hi.  I've been experimenting with using Reactive as the basis of a network
> daemon I'm working on, with the top-level type Event Packet -> Event Packet.
>
> I've run into a difficult-to-debug strictness problem with the Monad instance
> for EventG, at least when used together with makeEvent.  Here's a minimal test
> program that shows the problem:
>
I can't comment on this exact bug, but in general the Monad instance
for Event is semantically broken.

There's a perfectly reasonable joinE function, yes, but as I
understand it combining it with the perfectly reasonable Applicative
instance produces an illegal monad. Thus, in general, you should avoid
the Monad instance and use joinE directly when required.

--
Svein Ove Aas

Chris Lesniewski-Laas

unread,
Jul 10, 2009, 10:54:22 AM7/10/09
to sve...@gmail.com, reac...@haskell.org
On Fri, Jul 10, 2009 at 04:47:49PM +0200, Svein Ove Aas wrote:
> I can't comment on this exact bug, but in general the Monad instance
> for Event is semantically broken.
>
> There's a perfectly reasonable joinE function, yes, but as I
> understand it combining it with the perfectly reasonable Applicative
> instance produces an illegal monad. Thus, in general, you should avoid
> the Monad instance and use joinE directly when required.

For what it's worth, the same problem appears to be present if I use
joinE directly instead of join/>>=. I've experimented a lot with
variations on joinE's implementation.

If it is going to be possible for one input packet to result in multiple
output packets, I'm going to need some function from Event (Event a) to
Event a. switchE is clearly not it. joinE seems to be the right idea,
although I'm unclear on the semantics when an inner event has
occurrences predating its own occurrence in the Event (Event a).

Chris

Conal Elliott

unread,
Jul 11, 2009, 11:10:12 AM7/11/09
to Chris Lesniewski-Laas, reac...@haskell.org
Hi Chris,

Your use for joinE is exactly the sort of thing I had in mind for the Event monad.  The semantics is to combine all of the generated events via mappend, which means the occurrences will be time-sorted.  But first, the inner events are time-tweaked so as not to pre-date the occurencs that gave birth to them.  Have you read "Push-pull functional reactive programming" (or its nearly-identical predecessor "Simply efficient functional reactivity)?  The semantics is spelled out precisely there, in terms of the monad instance for Future.  The latter monad is determined by the semantics of Future (time/value pairs, with a suitable monoid for time), according to the "type class morphism" property (subject of another paper).  If there's something in the paper that's not clear to you, feel free to ask for clarification.

I think there's a misunderstanding behind Svein's comment about a conflict between Applicative & Monad instances for Event.  I gave both instances in the paper, and mentioned why I don't expect the Applicative instance to be very useful.  I hinted at an alternative (relative time, which simply changes the Time monoid from Max to Sum), which would lead to different Applicative & Monad instances -- again consistent with each other -- where I'd expect the Applicative instance to be more useful (because sums have more variety than maxima).

There is a problem with the monad associativity law in the current Event Monad semantics.  I doubt that semantic problem has anything to do with the implementation problem in joinE.  I'd guess there's a subtle laziness issue.

Regards,  - Conal
Reply all
Reply to author
Forward
0 new messages