I'm going to be writing a real-time game in Haskell using netwire and OpenGL. The basic idea is that each object will be represented by a wire, which will get some amount of data as input and output its state, and then I'll hook it all up into one big wire that gets the state of the GUI as input and outputs the world state, which I can then pass onto a renderer as well as some 'global' logic like collision detection.
One thing I'm not sure about is: how do I want to type the wires? Not all entities have the same input; the player is the only entity that can access the state of the key input, seeking missiles need the position of their target, etc.
What should I do?
The inhibition monoid e
is the type for inhibition exceptions. It's not something the wire produces, but takes about the same role as the e
in Either e a
. In other words, if you combine wires by <|>
, then the output types must be equal.
Let's say your GUI events are passed to the wire through input and you have a continuous key-down event. One way to model this is the most straightforward:
keyDown :: (Monad m, Monoid e) => Key -> Wire e m GameState ()
This wire takes the current game state as input and produces a ()
if the key is held down. While the key is not pressed, it simply inhibits. Most applications don't really care about why a wire inhibits, so most wires inhibit with mempty
.
A much more convenient way to express this event is by using a reader monad:
keyDown :: (Monoid e) => Key -> Wire e (Reader GameState) a a
What's really useful about this variant is that now you don't have to pass the game state as input. Instead this wire just acts like the identity wire when the even happens and inhibits when it doesn't:
quitScreen . keyDown Escape <|> mainGame
The idea is that when the escape key is pressed, then the event wire keyDown Escape
vanishes temporarily, because it acts like the identity wire. So the whole wire acts like quitScreen
assuming that it doesn't inhibit itself. Once the key is released, the event wire inhibits, so the composition with quitScreen
inhibits, too. Thus the whole wire acts like mainGame
.
If you want to limit the game state a wire can see, you can easily write a wire combinator for that:
trans :: (forall a. m' a -> m a) -> Wire e m' a b -> Wire e m a b
This allows you to apply withReaderT
:
trans (withReaderT fullGameStateToPartialGameState)