Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What invariants am I supposed to maintain when using Control.Wire.Unsafe.Event?

I'm using Netwire to write a program that is driven by events from the network. I guess there are three questions here:

  1. What makes Control.Wire.Unsafe.Event unsafe? Like the title says, what invariants do I need to maintain to use it safely?

  2. I decided I need something like this: mapMaybeE :: Monad m => (a -> Maybe b) -> Wire s e m (Event a) (Event b). The context is I have messages coming in from the network and I want to respond to only some of them. This is what I wrote:

    mapMaybeE :: Monad m => (a -> Maybe b) -> Wire s e m (Event a) (Event b)
    mapMaybeE f = arr go . arr (fmap f)
      where go WU.NoEvent = WU.NoEvent
            go (WU.Event Nothing) = WU.NoEvent
            go (WU.Event (Just a)) = WU.Event a
    

    Is that "legal"? Or am I supposed to inhibit if there's no event?

  3. Does Netwire make sense for this sort of problem? All the examples I've seen are of games that loop continuously. Here, I only want to step the wires when there's something to be done. Mostly, that will be network events, but I might also want to do things on a timer. E.g. an event comes in, then five seconds later the program does something. It shouldn't have to loop continuously until the time in the session is five seconds greater than when the event came in.

like image 836
Echo Nolan Avatar asked Apr 06 '18 21:04

Echo Nolan


1 Answers

For a lot of these answers, "correct" or "legal" depends on what you want your application to do. "Idiomatic" might be a more interesting question, but as the library author has passed away, it's difficult to answer these questions definitively. The following therefore only represents my experience and may not be correct:

  1. The "unsafe" part of Control.Wire.Unsafe.Event is the idea that you will be working with discrete instances in time, and you may not necessarily preserve the continuous time semantics that your program expects. In particular, there's no difference (from a type perspective) between an event happening in a simulation with a time state (the s in Wire s e m a b) that's represented as an Integer vs a Float, so you have to be careful to make sure that what you're doing makes sense for your application. The included general purpose combinators don't have that risk in the sense that they work with any sensible definition of "time". From the documentation of data Event:

    Denotes a stream of values, each together with time of occurrence. Since Event is commonly used for functional reactive programming it does not define most of the usual instances to protect continuous time and discrete event occurrence semantics.

    From the README:

    If you are a framework developer you can import the Control.Wire.Unsafe.Event module to implement your own events. A game engine may include events for key presses or certain things happening in the scene. However, as an application developer you should view this type as being opaque. This is necessary in order to protect continuous time semantics. You cannot access event values directly.

  2. There's certainly nothing "illegal" about doing it that way. When you inhibit is totally dependent on your application. The key difference being that if you compose wires that inhibit, the inhibition "bubbles up" to the first wire that handles it (such as with an Alternative: (<|>)). Producing NoEvent is fine if there's no event. :) The behavior you're looking for might be better modeled using the existing combinators dropWhileE and fmap from Control.Wire.Event though:

    mapMaybeE :: Monad m => (a -> Maybe b) -> Wire s e m (Event a) (Event b)
    mapMaybeE = arr (fmap fromJust) . dropWhileE isNothing . arr (fmap f)
    
  3. Yes, netwire makes sense for any problem where you have to simulate the state of a system that has time-dependent semantics. To expand,

    It shouldn't have to loop continuously until the time in the session is five seconds greater than when the event came in.

    Something is going to need to keep track of this timer, so you're not going to get around having a loop in one capacity or another. (Maybe you can have the operating system do it via a call to sleep or something, but internally there's still some loop somewhere...) netwire lets you model the behavior of your system explicitly and respond to these kinds of events: both network events and timer events. Because Haskell is lazy, if you compose wires such that "really complicated wire" depends on a timer, the result from "really complicated wire" will not be evaluated until the timer expires (see after.)

like image 79
Mokosha Avatar answered Oct 26 '22 23:10

Mokosha