I'm using Netwire to write a program that is driven by events from the network. I guess there are three questions here:
What makes Control.Wire.Unsafe.Event
unsafe? Like the title says, what invariants do I need to maintain to use it safely?
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?
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.
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:
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.
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)
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
.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With