I'm learning Haskell, and trying to write some event-driven programs.
The following code is from the tutorial: http://www.haskell.org/haskellwiki/OpenGLTutorial2
main = do
(progname,_) <- getArgsAndInitialize
initialDisplayMode $= [DoubleBuffered]
createWindow "Hello World"
reshapeCallback $= Just reshape
angle <- newIORef (0.0::GLfloat) -- 1
delta <- newIORef (0.1::GLfloat) -- 2
position <- newIORef (0.0::GLfloat, 0.0) -- 3
keyboardMouseCallback $= Just (keyboardMouse delta position)
idleCallback $= Just (idle angle delta)
displayCallback $= (display angle position)
mainLoop
The states are stored in IORefs, which makes it looks just like imperative language.
I'v heard that there are APIs other than this Graphics.UI.GLUT, (e.g. Reactive), but it looks very complicated.
My approach is that the lib provide a function runEventHandler, and the user writes a handler that accepts list of Events and convert them to IO ().
handler :: [Event] -> IO ()
runEventHandler :: ( [Event] -> IO () ) -> IO ()
And the main function should look like:
main = runEventHandler handler
Is there such libs?
I am currently implementing one using multi-threading, but I'm worrying that it might be poor in performance ...
reactive-banana is a mature library very similar to reactive. We won't try to reinvent an frp library; instead we'll explore how to integrate reactive-banana into a project for ourselves.
The big picture
To use a functional reactive programming library like reactive-banana with OpenGL we will divide the work into 4 parts, 2 of which already exist. We will use the existing GLUT library to interact with OpenGL, and the existing reactive-banana library for an implementation of functional reactive programming. We will provide 2 parts of our own. The first part we will provide is a framework that will connect GLUT to reactive-banana. The second part we will provide is the program that will be written in terms of the frp implementation (reactive-banana) and framework and GLUT types.
Both of the parts that we provide will be written in terms of the reactive-banana frp library. The library has two big ideas, Event t a and Behavior t a. Event t a represents events carrying data of type a that occur at different points in time. Behavior t a represents a time varying value of type a that is defined at all points in time. The t type argument we are required by the type system to preserve but otherwise ignore.
Most of the interface to Event and Behavior are hidden in their instances. Event is a Functor - we can fmap or <$> a function over the values of any Event.
fmap :: (a -> b) -> Event t a -> Event t b
Behavior is both Applicative and a Functor. We can fmap or <$> a function over all the values a Behavior takes on, can provide new constant unchanging values with pure, and calculate new Behaviors with <*>.
fmap :: (a -> b) -> Behavior t a -> Behavior t b
pure :: a -> Behavior t a
<*> :: Behavior t (a -> b) -> Behavior t a -> Behavior t b
There are a few other functions provided by reactive-banana that provide functionality that can't be represented in terms of base typeclasses. These introduce statefulness, combine Events together, and convert between Events and Behaviors.
State is introduced by accumE which takes an initial value and an Event of changes from the previous value to a new value and produces an Event of the new values. accumB produces a Behavior instead
accumE :: a -> Event t (a -> a) -> Event t a
accumB :: a -> Event t (a -> a) -> Behavior t a
union combines two event streams together
union :: Event t a -> Event t a -> Event t a
stepper can convert an Event to a Behavior holding the most recent value if we provide an initial value so that it is defined at all points in time. apply or <#> can convert a Behavior into an Event if we provide a series of Events at which to poll the current value of the Behavior.
stepper :: a -> Event t a -> Behavior t a
<#> :: Behavior t (a -> b) -> Event t a -> Event t b
The instances for Event and Behavior and the 19 functions in Reactive.Banana.Combinators make up the entire interface for functional reactive programming.
Overall, we will need the GLUT library and libraries used by the OpenGL example we are implementing, the reactive-banana library, the reactive-banana exports for making frameworks and the RankNTypes extension, a couple mechanisms for interthread communication, and the ability to read the system clock.
{-# LANGUAGE RankNTypes #-}
import Graphics.UI.GLUT
import Control.Monad
import Reactive.Banana
import Reactive.Banana.Frameworks
import Data.IORef
import Control.Concurrent.MVar
import Data.Time
The framework interface
Our framework will map the IO events from GLUT to reactive-banana Events and Behaviors. There are four GLUT events that the example uses - reshapeCallback, keyboardMouseCallback, idleCallback, and displayCallback. We will map these to Events and Behaviors.
reshapeCallback is run when the user resizes the window. As a callback, it required something of the type type ReshapeCallback = Size -> IO (). We will represent this as an Event t Size.
keyboardMouseCallback is run when the user provides keyboard input, moves the mouse, or clicks a mouse button. As a callback, it required something of the type type KeyboardMouseCallback = Key -> KeyState -> Modifiers -> Position -> IO (). We will represent this as an input with type Event t KeyboardMouse, where KeyboardMouse bundles together all of the arguments passed to the callback.
data KeyboardMouse = KeyboardMouse {
key :: Key,
keyState :: KeyState,
modifiers :: Modifiers,
pos :: Position
}
idleCallback is run when time passes. We will represent this as a behavior that tracks the amount of time that has passed, Behavior t DiffTime. Because it is a Behavior instead of an Event, our program won't be able to directly observe time passing. If this isn't desired, we could use an Event instead.
Bundling all of the inputs together we get
data Inputs t = Inputs {
keyboardMouse :: Event t KeyboardMouse,
time :: Behavior t DiffTime,
reshape :: Event t Size
}
displayCallback is different from the other callbacks; it isn't for the input to the program, but instead is for outputting what needs to be displayed. Since GLUT could run this at any time to try to display something on the screen, it makes sense for it to be defined at all points in time. We will represent this output with a Behavior t DisplayCallback.
There is one more output we will need - in response to events the example program occasionally produces other IO actions. We will allow the program to raise events to execute arbitrary IO with an Event t (IO ()).
Bundling both outputs together we get
data Outputs t = Outputs {
display :: Behavior t DisplayCallback,
whenIdle :: Event t (IO ())
}
Our framework will be invoked by passing it a program with the type forall t. Inputs t -> Outputs t. We will define program and reactiveGLUT in the next two sections.
main :: IO ()
main = do
(progname,_) <- getArgsAndInitialize
initialDisplayMode $= [DoubleBuffered]
createWindow "Hello World"
reactiveGLUT program
The program
The program will use reactive-banana to map the Inputs to the Outputs. To get started porting the tutorial code, we'll remove the IORefs from cubes and rename reshape to onReshape since it conflicts with a name from our framework interface.
cubes :: GLfloat -> (GLfloat, GLfloat) -> DisplayCallback
cubes a (x',y') = do
clear [ColorBuffer]
loadIdentity
translate $ Vector3 x' y' 0
preservingMatrix $ do
rotate a $ Vector3 0 0 1
scale 0.7 0.7 (0.7::GLfloat)
forM_ (points 7) $ \(x,y,z) -> preservingMatrix $ do
color $ Color3 ((x+1)/2) ((y+1)/2) ((z+1)/2)
translate $ Vector3 x y z
cube 0.1
swapBuffers
onReshape :: ReshapeCallback
onReshape size = do
viewport $= (Position 0 0, size)
keyboardMouse will be completely replaced by positionChange and angleSpeedChange. These convert a KeyboardMouse event into a change to make to either the position or the speed the cubes are rotating. When no change is needed for an event, they return Nothing.
positionChange :: Fractional a => KeyboardMouse -> Maybe ((a, a) -> (a, a))
positionChange (KeyboardMouse (SpecialKey k) Down _ _) = case k of
KeyLeft -> Just $ \(x,y) -> (x-0.1,y)
KeyRight -> Just $ \(x,y) -> (x+0.1,y)
KeyUp -> Just $ \(x,y) -> (x,y+0.1)
KeyDown -> Just $ \(x,y) -> (x,y-0.1)
_ -> Nothing
positionChange _ = Nothing
angleSpeedChange :: Num a => KeyboardMouse -> Maybe (a -> a)
angleSpeedChange (KeyboardMouse (Char c) Down _ _) = case c of
' ' -> Just negate
'+' -> Just (+1)
'-' -> Just (subtract 1)
_ -> Nothing
angleSpeedChange _ = Nothing
Calculating the position is fairly easy, we accumulate the changes from the keyboard inputs. filterJust :: Event t (Maybe a) -> Event t a throws out the events that we weren't interested in.
positionB :: Fractional a => Inputs t -> Behavior t (a, a)
positionB = accumB (0.0, 0.0) . filterJust . fmap positionChange . keyboardMouse
We'll calculate the angle of the rotating cubes a bit differently. We'll remember the time and angle when the speed changes, apply a function that calculates the difference in angle to the difference in times, and add that to the initial angle.
angleCalculation :: (Num a, Num b) => a -> b -> (a -> b) -> a -> b
angleCalculation a0 b0 f a1 = f (a1 - a0) + b0
Calculating the angle is a bit more difficult. First we compute an event, angleF :: Event t (DiffTime -> GLfloat), holding a function from a difference between times to a difference between angles. We lift and apply our angleCalculation to the current time and angle, and poll that at each occurrence of the angleF event. We convert the polled function into a Behavior with stepper and apply it to the current time.
angleB :: Fractional a => Inputs t -> Behavior t a
angleB inputs = angle
where
initialSpeed = 2
angleSpeed = accumE initialSpeed . filterJust . fmap angleSpeedChange . keyboardMouse $ inputs
scaleSpeed x y = 10 * x * realToFrac y
angleF = scaleSpeed <$> angleSpeed
angleSteps = (angleCalculation <$> time inputs <*> angle) <#> angleF
angle = stepper (scaleSpeed initialSpeed) angleSteps <*> time inputs
The whole program maps Inputs to Outputs. It says that the behavior for what to display is cubes lifted and applied to the angle and position. The Event for other IO side effects is onReshape every time the reshape event happens.
program :: Inputs t -> Outputs t
program inputs = outputs
where
outputs = Outputs {
display = cubes <$> angleB inputs <*> positionB inputs,
whenIdle = onReshape <$> reshape inputs
}
The framework
Our framework accepts a program with the type forall t. Inputs t -> Outputs t and runs it. To implement the framework, we use the functions in Reactive.Banana.Frameworks. These functions allow us to raise Events from IO and run IO actions in response to Events. We can make Behaviors from Events and poll Behaviors when Events occur using the functions from Reactive.Banana.Combinators.
reactiveGLUT :: (forall t. Inputs t -> Outputs t) -> IO ()
reactiveGLUT program = do
-- Initial values
initialTime <- getCurrentTime
-- Events
(addKeyboardMouse, raiseKeyboardMouse) <- newAddHandler
(addTime, raiseTime) <- newAddHandler
(addReshape, raiseReshape) <- newAddHandler
(addDisplay, raiseDisplay) <- newAddHandler
newAddHandler creates a handle with which to talk about an Event t a, and a function to raise the event of type a -> IO (). We make the obvious events for keyboard and mouse input, idle time passing, and the window shape changing. We also make an event that we will use to poll the display Behavior when we need to run it in the displayCallback.
We have one tricky problem to overcome - OpenGL requires all the UI interaction to happen in a specific thread, but we aren't sure what thread the actions we bind to reactive-banana events will happen in. We'll use a couple of variables shared across threads to make sure the Output IO is run in the OpenGL thread. For display output, we'll use an MVar to store the polled display action. For IO actions that are queued in whenIdle we'll accumulate them in an IORef,
-- output variables and how to write to them
displayVar <- newEmptyMVar
whenIdleRef <- newIORef (return ())
let
setDisplay = putMVar displayVar
runDisplay = takeMVar displayVar >>= id
addWhenIdle y = atomicModifyIORef' whenIdleRef (\x -> (x >> y, ()))
runWhenIdle = atomicModifyIORef' whenIdleRef (\x -> (return (), x)) >>= id
Our whole network consists of the following parts. First we create Events (using fromAddHandler) or Behaviors (using fromChanges) for each of the Inputs and an Event for polling the output display. We perform a small amount of processing to simplify the clock. We apply the program to the inputs we prepared to get the program's Outputs. Using <#, we poll the display whenever our display event happens. Finally, reactimate tells reactive-banana to run setDisplay or addWhenIdle whenever the corresponsonding Event occurs. Once we have described the network we compile and actuate it.
-- Reactive network for GLUT programs
let networkDescription :: forall t. Frameworks t => Moment t ()
networkDescription = do
keyboardMouseEvent <- fromAddHandler addKeyboardMouse
clock <- fromChanges initialTime addTime
reshapeEvent <- fromAddHandler addReshape
displayEvent <- fromAddHandler addDisplay
let
diffTime = realToFrac . (flip diffUTCTime) initialTime <$> clock
inputs = Inputs keyboardMouseEvent diffTime reshapeEvent
outputs = program inputs
displayPoll = display outputs <# displayEvent
reactimate $ fmap setDisplay displayPoll
reactimate $ fmap addWhenIdle (whenIdle outputs)
network <- compile networkDescription
actuate network
For each of the GLUT callbacks we are interested in we raise the corresponding reactive-banana Event. For the idle callback we also run any queued events. For the display callback, we run the polled DisplayCallback.
-- Handle GLUT events
keyboardMouseCallback $= Just (\k ks m p -> raiseKeyboardMouse (KeyboardMouse k ks m p))
idleCallback $= Just (do
getCurrentTime >>= raiseTime
runWhenIdle
postRedisplay Nothing)
reshapeCallback $= Just raiseReshape
displayCallback $= do
raiseDisplay ()
runDisplay
mainLoop
The rest of the example
The rest of the tutorial code can be repeated verbatim
vertex3f :: (GLfloat, GLfloat, GLfloat) -> IO ()
vertex3f (x, y, z) = vertex $ Vertex3 x y z
points :: Int -> [(GLfloat,GLfloat,GLfloat)]
points n = [ (sin (2*pi*k/n'), cos (2*pi*k/n'), 0) | k <- [1..n'] ]
where n' = fromIntegral n
cube :: GLfloat -> IO ()
cube w = renderPrimitive Quads $ mapM_ vertex3f
[ ( w, w, w), ( w, w,-w), ( w,-w,-w), ( w,-w, w),
( w, w, w), ( w, w,-w), (-w, w,-w), (-w, w, w),
( w, w, w), ( w,-w, w), (-w,-w, w), (-w, w, w),
(-w, w, w), (-w, w,-w), (-w,-w,-w), (-w,-w, w),
( w,-w, w), ( w,-w,-w), (-w,-w,-w), (-w,-w, w),
( w, w,-w), ( w,-w,-w), (-w,-w,-w), (-w, w,-w) ]
Related
I need to make each instance of Sphere get a unique identifier so that no two Spheres are equal. I won't know ahead of time how many spheres I'll need to make so will need to make them one at a time, but still increment the identifier.
Most solutions I've tried have this issue where I end up with an IO a and need the unsafePerformIO to get the value.
This code comes close, but the resulting identifier is always the same:
module Shape ( Sphere (..)
, sphere
, newID
) where
import System.Random
import System.IO.Unsafe (unsafePerformIO)
data Sphere = Sphere { identifier :: Int
} deriving (Show, Eq)
sphere :: Sphere
sphere = Sphere { identifier = newID }
newID :: Int
newID = unsafePerformIO (randomRIO (1, maxBound :: Int))
This would work as well, and works great in the REPL, but when I put it in a function, it only returns a new value the first time and the same value after that.
import Data.Unique
sphere = Sphere { identifier = (hashUnique $ unsafePerformIO newUnique) }
I know think this all leads to the State Monad, but I don't understand that yet. Is there no other way that will "get the job done", without biting off all the other monad stuff?
First of all, don’t use unsafePerformIO here. It doesn’t do what you want anyway: it doesn’t “get the a out of an IO a”, since an IO a doesn’t contain an a; rather, unsafePerformIO hides an IO action behind a magical value that executes the action when somebody evaluates the value, which could happen multiple times or never because of laziness.
Is there no other way that will "get the job done", without biting off all the other monad stuff?
Not really. You’re going to have to maintain some kind of state if you want to generate unique IDs. (You may be able to avoid needing unique IDs altogether, but I don’t have enough context to say.) State can be handled in a few ways: manually passing values around, using State to simplify that pattern, or using IO.
Suppose we want to generate sequential IDs. Then the state is just an integer. A function that generates a fresh ID can simply take that state as input and return an updated state. I think you’ll see straight away why that’s too simple, so we tend to avoid writing code like this:
-- Differentiating “the next-ID state” from “some ID” for clarity.
newtype IdState = IdState Id
type Id = Int
-- Return new sphere and updated state.
newSphere :: IdState -> (Sphere, IdState)
newSphere s0 = let
(i, s1) = newId s0
in (Sphere i, s1)
-- Return new ID and updated state.
newId :: IdState -> (Id, IdState)
newId (IdState i) = (i, IdState (i + 1))
newSpheres3 :: IdState -> ((Sphere, Sphere, Sphere), IdState)
newSpheres3 s0 = let
(sphere1, s1) = newSphere s0
(sphere2, s2) = newSphere s1
(sphere3, s3) = newSphere s2
in ((sphere1, sphere2, sphere3), s3)
main :: IO ()
main = do
-- Generate some spheres with an initial ID of 0.
-- Ignore the final state with ‘_’.
let (spheres, _) = newSpheres3 (IdState 0)
-- Do stuff with them.
print spheres
Obviously this is very repetitive and error-prone, since we have to pass the correct state along at each step. The State type has a Monad instance that abstracts out this repetitive pattern and lets you use do notation instead:
import Control.Monad.Trans.State (State, evalState, state)
newSphere :: State IdState Sphere
newSphere = do
i <- newId
pure (Sphere i)
-- or:
-- newSphere = fmap Sphere newId
-- newSphere = Sphere <$> newId
-- Same function as before, just wrapped in ‘State’.
newId :: State IdState Id
newId = state (\ (IdState i) -> (i, IdState (i + 1)))
-- Much simpler!
newSpheres3 :: State IdState (Sphere, Sphere, Sphere)
newSpheres3 = do
sphere1 <- newSphere
sphere2 <- newSphere
sphere3 <- newSphere
pure (sphere1, sphere2, sphere3)
-- or:
-- newSpheres3 = (,,) <$> newSphere <*> newSphere <*> newSphere
main :: IO ()
main = do
-- Run the ‘State’ action and discard the final state.
let spheres = evalState newSpheres3 (IdState 0)
-- Again, do stuff with the results.
print spheres
State is what I would reach for normally, since it can be used within pure code, and combined with other effects without much trouble using StateT, and because it’s actually immutable under the hood, just an abstraction on top of passing values around, you can easily and efficiently save and roll back states.
If you want to use randomness, Unique, or make your state actually mutable, you generally have to use IO, because IO is specifically about breaking referential transparency like that, typically by interacting with the outside world or other threads. (There are also alternatives like ST for putting imperative code behind a pure API, or concurrency APIs like Control.Concurrent.STM.STM, Control.Concurrent.Async.Async, and Data.LVish.Par, but I won’t go into them here.)
Fortunately, that’s very similar to the State code above, so if you understand how to use one, it should be easier to understand the other.
With random IDs using IO (not guaranteed to be unique):
import System.Random
newSphere :: IO Sphere
newSphere = Sphere <$> newId
newId :: IO Id
newId = randomRIO (1, maxBound :: Id)
newSpheres3 :: IO (Sphere, Sphere, Sphere)
newSpheres3 = (,,) <$> newSphere <*> newSphere <*> newSphere
main :: IO ()
main = do
spheres <- newSpheres3
print spheres
With Unique IDs (also not guaranteed to be unique, but unlikely to collide):
import Data.Unique
newSphere :: IO Sphere
newSphere = Sphere <$> newId
newId :: IO Id
newId = hashUnique <$> newUnique
-- …
With sequential IDs, using a mutable IORef:
import Data.IORef
newtype IdSource = IdSource (IORef Id)
newSphere :: IdSource -> IO Sphere
newSphere s = Sphere <$> newId s
newId :: IdSource -> IO Id
newId (IdSource ref) = do
i <- readIORef ref
writeIORef ref (i + 1)
pure i
-- …
You’re going to have to understand how to use do notation and functors, applicatives, and monads at some point, because that’s just how effects are represented in Haskell. You don’t necessarily need to understand every detail of how they work internally in order to just use them, though. I got pretty far when I was learning Haskell with some rules of thumb, like:
A do statement can be:
An action: (action :: m a)
Often m () in the middle
Often pure (expression :: a) :: m a at the end
A let binding for expressions: let (var :: a) = (expression :: a)
A monadic binding for actions: (var :: a) <- (action :: m a)
f <$> action applies a pure function to an action, short for do { x <- action; pure (f x) }
f <$> action1 <*> action2 applies a pure function of multiple arguments to multiple actions, short for do { x <- action1; y <- action2; pure (f x y) }
action2 =<< action1 is short for do { x <- action1; action2 x }
I'm using reactive-banana and sdl2 (using this glue library) for a game-like project. A Behavior is created for the "absolute mouse location", as well as a Behavior for the "relative mouse location" (a.k.a. the mouse movement). When not using FRP this works well, but with FRP the "relative mouse location" becomes a problem: it seems only a small amount of the data comes through. I suspect this happens because the underlying "SDL events" (that we represent with a Behavior) do not line up nicely with the tick Events.
So I want to calculate my own mouse movement, by simply comparing the mouse location at the current tick with the location at the previous tick. I'm not sure if this will solve my problems, but I have good hope :)
First of all I'm lost on how to approach it: the State monad, or an IORef, or does reactive-banana provide another means?
I'll give a small excerpt of the code I currently have:
makeNetwork :: GraphicsData -> SDLEventSource -> MomentIO ()
makeNetwork gd sdlEventSource = mdo
tickE <- tickEvent sdlEventSource
mouseMovementB <- fromPoll SDL.getRelativeMouseLocation
mousePositionB <- fromPoll SDL.getAbsoluteMouseLocation
let mousePositionE = mousePositionB <# tickE
mouseMovementE = mouseMovementB <# tickE -- this yields flaky data
-- ... the rest of the network description left out ...
As explained above I'd like to express mouseMovementE in terms of the mousePositionB at current tickE (known as mousePositionE) and the mousePositionE value at the previous tickE.
Any help is greatly appreciated!
You are looking for accumE which builds events from streams of events. I highly recommend reading the recursion section of the documentation which describes how it's implemented in terms of stepper and apply.
accumE :: MonadMoment m => a -> Event (a -> a) -> m (Event a)
-- starting value --^ | |
-- stream of events that modify it --^ |
-- resulting events --^
To compute the difference between two points with accumE we'll need to keep track of the previous point. We'll also keep track of the current point. This will keep a little two-item history of the most recent events.
(Point V2 CInt , Point V2 CInt)
-- previous value, current value
edges :: MonadMoment m => a -> Event a -> m (Event (a, a))
edges initial later = accumE (initial, initial) (shift <$> later)
where
shift x2 (x0, x1) = (x1, x2)
To get the difference we'll subtract the previous one from the current one. This will give a complete network like
makeNetwork :: GraphicsData -> SDLEventSource -> MomentIO ()
makeNetwork gd sdlEventSource = mdo
tickE <- tickEvent sdlEventSource
mousePositionB <- fromPoll SDL.getAbsoluteMouseLocation
let mousePositionE = mousePositionB <# tickE
mouseHistoryE <- edges zero mousePositionE
let mouseMovementE = (\(x0, x1) -> x1 ^-^ x0) <$> mouseHistoryE
-- ...
zero and ^-^ come from Linear.Vector
Starting from a previous question here:
Reactive Banana: how to use values from a remote API and merge them in the event stream
I have a bit different problem now: How can I use the Behaviour output as input for an IO operation and finally display the IO operation's result?
Below is the code from the previous answer changed with a second output:
import System.Random
type RemoteValue = Int
-- generate a random value within [0, 10)
getRemoteApiValue :: IO RemoteValue
getRemoteApiValue = (`mod` 10) <$> randomIO
getAnotherRemoteApiValue :: AppState -> IO RemoteValue
getAnotherRemoteApiValue state = (`mod` 10) <$> randomIO + count state
data AppState = AppState { count :: Int } deriving Show
transformState :: RemoteValue -> AppState -> AppState
transformState v (AppState x) = AppState $ x + v
main :: IO ()
main = start $ do
f <- frame [text := "AppState"]
myButton <- button f [text := "Go"]
output <- staticText f []
output2 <- staticText f []
set f [layout := minsize (sz 300 200)
$ margin 10
$ column 5 [widget myButton, widget output, widget output2]]
let networkDescription :: forall t. Frameworks t => Moment t ()
networkDescription = do
ebt <- event0 myButton command
remoteValueB <- fromPoll getRemoteApiValue
myRemoteValue <- changes remoteValueB
let
events = transformState <$> remoteValueB <# ebt
coreOfTheApp :: Behavior t AppState
coreOfTheApp = accumB (AppState 0) events
sink output [text :== show <$> coreOfTheApp]
sink output2 [text :== show <$> reactimate ( getAnotherRemoteApiValue <#> coreOfTheApp)]
network <- compile networkDescription
actuate network
As you can see what I am trying to do it is using the new state of the application -> getAnotherRemoteApiValue -> show. But it doesn't work.
Is actually possible doing that?
UPDATE
Based on the Erik Allik and Heinrich Apfelmus below answers I have the current code situation - that works :) :
{-# LANGUAGE ScopedTypeVariables #-}
module Main where
import System.Random
import Graphics.UI.WX hiding (Event, newEvent)
import Reactive.Banana
import Reactive.Banana.WX
data AppState = AppState { count :: Int } deriving Show
initialState :: AppState
initialState = AppState 0
transformState :: RemoteValue -> AppState -> AppState
transformState v (AppState x) = AppState $ x + v
type RemoteValue = Int
main :: IO ()
main = start $ do
f <- frame [text := "AppState"]
myButton <- button f [text := "Go"]
output1 <- staticText f []
output2 <- staticText f []
set f [layout := minsize (sz 300 200)
$ margin 10
$ column 5 [widget myButton, widget output1, widget output2]]
let networkDescription :: forall t. Frameworks t => Moment t ()
networkDescription = do
ebt <- event0 myButton command
remoteValue1B <- fromPoll getRemoteApiValue
let remoteValue1E = remoteValue1B <# ebt
appStateE = accumE initialState $ transformState <$> remoteValue1E
appStateB = stepper initialState appStateE
mapIO' :: (a -> IO b) -> Event t a -> Moment t (Event t b)
mapIO' ioFunc e1 = do
(e2, handler) <- newEvent
reactimate $ (\a -> ioFunc a >>= handler) <$> e1
return e2
remoteValue2E <- mapIO' getAnotherRemoteApiValue appStateE
let remoteValue2B = stepper Nothing $ Just <$> remoteValue2E
sink output1 [text :== show <$> appStateB]
sink output2 [text :== show <$> remoteValue2B]
network <- compile networkDescription
actuate network
getRemoteApiValue :: IO RemoteValue
getRemoteApiValue = do
putStrLn "getRemoteApiValue"
(`mod` 10) <$> randomIO
getAnotherRemoteApiValue :: AppState -> IO RemoteValue
getAnotherRemoteApiValue state = do
putStrLn $ "getAnotherRemoteApiValue: state = " ++ show state
return $ count state
The fundamental problem is a conceptual one: FRP Events and Behaviors can only be combined in a pure way. In principle, it is not possible to have a function of type, say
mapIO' :: (a -> IO b) -> Event a -> Event b
because the order in which the corresponding IO actions are to be executed is undefined.
In practice, it may sometimes be useful to perform IO while combining Events and Behaviors. The execute combinator can do this, as #ErikAllik indicates. Depending on the nature of getAnotherRemoteApiValue, this may be the right thing to do, in particular if this is function is idempotent, or does a quick lookup from location in RAM.
However, if the computation is more involved, then it is probably better to use reactimate to perform the IO computation. Using newEvent to create an AddHandler, we can give an implementation of the mapIO' function:
mapIO' :: (a -> IO b) -> Event a -> MomentIO (Event b)
mapIO' f e1 = do
(e2, handler) <- newEvent
reactimate $ (\a -> f a >>= handler) <$> e1
return e2
The key difference to the pure combinator
fmap :: (a -> b) -> Event a -> Event b
is that the latter guarantees that the input and result events occur simultaneously, while the former gives absolutely no guarantee about when the result event occurs in relation to other events in the network.
Note that execute also guarantees that input and result are have simultaneous occurrences, but places informal restrictions on the IO allowed.
With this trick of combining reactimate with newEvent a similar combinator can be written for Behaviors in a similar fashion. Keep in mind that the toolbox from Reactive.Banana.Frameworks is only appropriate if you are dealing with IO actions whose precise order will necessarily be undefined.
(To keep this answer current, I have used the type signatures from the upcoming reactive-banana 1.0. In version 0.9, the type signature for mapIO' is
mapIO' :: Frameworks t => (a -> IO b) -> Event t a -> Moment t (Event t b)
)
TL;DR: scroll down to the ANSWER: section for a solution along with an explanation.
First of all
getAnotherRemoteApiValue state = (`mod` 10) <$> randomIO + count state
is invalid (i.e. does not typecheck) for reasons completely unrelated to FRP or reactive-banana: you cannot add an Int to an IO Int — just as you can't apply mod 10 to an IO Int directly, which is exactly why, in the answer to your original question, I used <$> (which is another name for fmap from Functor).
I strongly recommend you look up and understand the purpose/meaning of <$>, along with <*> and some other Functor and Applicative type class methods — FRP (at least the way it is designed in reactive-banana) builds heavily upon Functors and Applicatives (and sometimes Monads, Arrows and possibly some other more novel foundation), hence if you don't completely understand those, you won't ever become proficient with FRP.
Secondly, I'm not sure why you're using coreOfTheApp for sink output2 ... — the coreOfTheApp value is related to the other API value.
Thirdly, how should the other API value be displayed? Or, more specifically, when should it be displayed? Your first API value is displayed when the button is clicked but there's no button for the second one — do you want the same button to trigger the API call and display update? Do you want another button? Or do you want it to be polled every n unit of time and simply auto-updated in the UI?
Lastly, reactimate is meant for converting a Behavior into an IO action, which is not what you want, because you already have the show helper and don't need to setText or smth on the static label. In other words, what you need for the second API value is the same as before, except you need to pass something from the app state along with the request to the external API, but aside from that difference, you can still just keep showing the (other) API value using show as normal.
ANSWER:
As to how to convert getAnotherRemoteApiValue :: AppState -> IO RemoteValue into an Event t Int similar to the original remoteValueE:
I first tried to go via IORefs and using changes+reactimate', but that quickly turned out to a dead end (besides being ugly and overly complicated): output2 was always updated one FRP "cycle" too late, so it was always one "version" behind in the UI.
I then, with the help of Oliver Charles (ocharles) on #haskell-game on FreeNode, turned to execute:
execute :: Event t (FrameworksMoment a) -> Moment t (Event t a)
which I still don't fully grasp yet, but it works:
let x = fmap (\s -> FrameworksMoment $ liftIO $ getAnotherRemoteApiValue s)
(appStateB <# ebt)
remoteValue2E <- execute x
so the same button would trigger both actions. But the problem with that quickly turned out to be the same as with the IORef based solution — since the same button would trigger a pair of events, and one event inside that pair depended on the other, the contents of output2 was still one version behind.
I then realised the events relatede to output2 need to be triggered after any events related to output1. However, it's impossible to go from Behavior t a -> Event t a; in other words, once you have a behavior, you can't (easily?) obtain an event from that (except with changes, but changes is tied to reactimate/reactimate', which is not useful here).
I finally noticed that I was essentially "throwing away" an intermediate Event at this line:
appStateB = accumB initialState $ transformState <$> remoteValue1E
by replacing it with
appStateE = accumE initialState $ transformState <$> remoteValue1E
appStateB = stepper initialState -- there seems to be no way to eliminate the initialState duplication but that's fine
so I still had the exact same appStateB, which is used as previously, but I could then also rely on appStateE to reliably trigger further events that rely on the AppState:
let x = fmap (\s -> FrameworksMoment $ liftIO $ getAnotherRemoteApiValue s)
appStateE
remoteValue2E <- execute x
The final sink output2 line looks like:
sink output2 [text :== show <$> remoteValue2B]
All of the code can be seen at http://lpaste.net/142202, with debug output still enabled.
Note that the (\s -> FrameworkMoment $ liftIO $ getAnotherRemoteApiValue s) lambda cannot be converted to point-free style for reasons related to RankN types. I was told this problem will go away in reactive-banana 1.0 because there will be no FrameworkMoment helper type.
I am learning reactive-banana. In order to understand the library I have decide to implement a dummy application that would increase a counter whenever someone pushes a button.
The UI library I am using is Gtk but that is not relevant for the explanation.
Here is the very simple implementation that I have come up with:
import Graphics.UI.Gtk
import Reactive.Banana
import Reactive.Banana.Frameworks
makeNetworkDescription addEvent = do
eClick <- fromAddHandler addEvent
reactimate $ (putStrLn . show) <$> (accumE 0 ((+1) <$ eClick))
main :: IO ()
main = do
(addHandler, fireEvent) <- newAddHandler
initGUI
network <- compile $ makeNetworkDescription addHandler
actuate network
window <- windowNew
button <- buttonNew
set window [ containerBorderWidth := 10, containerChild := button ]
set button [ buttonLabel := "Add One" ]
onClicked button $ fireEvent ()
onDestroy window mainQuit
widgetShowAll window
mainGUI
This just dumps the result in the shell. I came up to this solution reading the article by Heinrich Apfelmus. Notice that in my example I have not used a single Behavior.
In the article there is an example of a network:
makeNetworkDescription addKeyEvent = do
eKey <- fromAddHandler addKeyEvent
let
eOctaveChange = filterMapJust getOctaveChange eKey
bOctave = accumB 3 (changeOctave <$> eOctaveChange)
ePitch = filterMapJust (`lookup` charPitches) eKey
bPitch = stepper PC ePitch
bNote = Note <$> bOctave <*> bPitch
eNoteChanged <- changes bNote
reactimate' $ fmap (\n -> putStrLn ("Now playing " ++ show n))
<$> eNoteChanged
The example show a stepper that transforms an Event into a Behavior and brings back an Event using changes. In the above example we could have used only Event and I guess that it would have made no difference (unless I am not understanding something).
So could someone can shed some light on when to use Behavior and why? Should we convert all Events as soon as possible?
In my little experiment I don't see where Behavior can be used.
Thanks
Anytime the FRP network "does something" in Reactive Banana it's because it's reacting to some input event. And the only way it does anything observable outside the system is by wiring up an external system to react to events it generates (using reactimate).
So if all you're doing is immediately reacting to an input event by producing an output event, then no, you won't find much reason to use Behaviour.
Behaviour is very useful for producing program behaviour that depends on multiple event streams, where you have to remember that events happen at different times.
An Event has occurrences; specific instants of time where it has a value. A Behaviour has a value at all points in time, with no instants of time that are special (except with changes, which is convenient but kind of model-breaking).
A simple example familiar from many GUIs would be if I want to react to mouse clicks and have shift-click do something different from a click when the shift key is not held. With a Behaviour holding a value indicating whether the shift key is held down, this is trivial. If I just had Events for shift key press/release and for mouse clicks it's much harder.
In addition to being harder, it's much more low level. Why should I have to do complicated fiddling just to implement a simple concept like shift-click? The choice between Behaviour and Event is a helpful abstraction for implementing your program's concepts in terms that map more closely to the way you think about them outside the programming world.
An example here would be a movable object in a game world. I could have an Event Position representing all the times it moves. Or I could just have a Behaviour Position representing where it is at all times. Usually I'll be thinking of the object as having a position at all times, so Behaviour is a better conceptual fit.
Another place Behaviours are useful is for representing external observations your program can make, where you can only check the "current" value (because the external system won't notify you when changes occur).
For an example, let's say your program has to keep tabs on a temperature sensor and avoid starting a job when the temperature is too high. With an Event Temperature I'll have decide up front how often to poll the temperature sensor (or in response to what). And then have all the same issues as in my other examples about having to manually do something to make the last temperature reading available to the event that decides whether or not to start a job. Or I could use fromPoll to make a Behaviour Temperature. Now I've got a value that represents the time-varying value of the temperature, and I've completely abstracted away from polling the sensor; Reactive Banana itself takes care of polling the sensor as often as it might be needed without me needing to impending any logic for that at all!
Behaviors have a value all the time, whereas Events only have a value at an instant.
Think of it like you would in a spreadsheet - most of the data exists as stable values (Behaviors) that hang around and get updated whenever necessary. (In FRP though, the dependency can go either way without circular reference problems - the data is updated flowing from the changed value to unchanged ones.) You can additionally add code that fires when you press a button or do something else, but most of the data is available all the time.
Certainly you could do all that with just events - when this changes, read this value and that value and output this value, but it's just cleaner to express those relationships declaratively and let the spreadsheet or compiler worry about when to update stuff for you.
stepper is for changing things that happen into values in cells, and change is for watching cells and triggering actions. Your example where the output is text on a command line isn't particularly affected by the lack of persistent data, because the output comes in bursts anyway.
If however you have a graphical user interface, the event-only model, whilst certainly possible, and indeed common, is a little cumbersome compared to the FRP model. In FRP you just specify the relationships between things without being explicit about updates.
It's not necessary to have Behaviors, and analogously you could program an Excel spreadsheet entirely in VBA with no formulae. It's just nicer with the data permanence and equational specification. Once you're used to the new paradigm, you'll not want to go back to manually chasing dependencies and updating stuff.
When you have only 1 Event, or multiple Events that happen simultaneously, or multiple Events of the same type, it's easy to just union or otherwise combine them into a resulting Event, then pass to reactimate and immediately output it. But what if you have 2 Events of 2 different types happening at different times? Then combining them into a resulting Event that you can pass to reactimate becomes an unnecessary complication.
I recommend you to actually try and implement the synthesizer from FRP explanation using reactive-banana with only Events and no Behaviors, you'll quickly see that Behaviors simplify the unnecessary Event manipulations.
Say we have 2 Events, outputting Octave (type synonym for Int) and Pitch (type synonym to Char). User presses keys from a to g to set current pitch, or presses + or - to increment or decrement current octave. The program should output current pitch and current octave, like a0, b2, or f7. Let's say the user pressed these keys in various combinations during different times, so we ended up with 2 event streams (Events) like that:
+ - + -- octave stream (time goes from left to right)
b c -- pitch stream
Every time user presses a key, we output current octave and pitch. But what should be the result event? Suppose default pitch is a and default octave is 0. We should end up with an event stream that looks like this:
a1 b1 b0 c0 c1 -- a1 corresponds to + event, b1 to b, b0 to -, etc
Simple character input/output
Let's try to implement the synthesizer from scratch and see if we can do it without Behaviors. Let's first write a program, where you put a character, press Enter, the program outputs it, and asks for a character again:
import System.IO
import Control.Monad (forever)
main :: IO ()
main = do
-- Terminal config to make output cleaner
hSetEcho stdin False
hSetBuffering stdin NoBuffering
-- Event loop
forever (getChar >>= putChar)
Simple event-network
Let's do the above but with an event-network, to illustrate them.
import Control.Monad (forever)
import System.IO (BufferMode(..), hSetEcho, hSetBuffering, stdin)
import Control.Event.Handler (newAddHandler)
import Reactive.Banana
import Reactive.Banana.Frameworks
makeNetworkDescription :: Frameworks t => AddHandler Char -> Moment t ()
makeNetworkDescription myAddHandler = do
event <- fromAddHandler myAddHandler
reactimate $ putChar <$> event
main :: IO ()
main = do
-- Terminal config to make output cleaner
hSetEcho stdin False
hSetBuffering stdin NoBuffering
-- Event loop
(myAddHandler, myHandler) <- newAddHandler
network <- compile (makeNetworkDescription myAddHandler)
actuate network
forever (getChar >>= myHandler)
A network is where all your events and behaviors live and interact with each other. They can only do that inside Moment monadic context. In tutorial Functional Reactive Programming kick-starter guide the analogy for event-network is a human brain. A human brain is where all event streams and behaviors interleave with each other, but the only way to access the brain is through receptors, which act as event source (input).
Now, before we proceed, carefully check out the types of the most important functions of the above snippet:
type Handler a = a -> IO ()
newtype AddHandler a = AddHandler { register :: Handler a -> IO (IO ()) }
newAddHandler :: IO (AddHandler a, Handler a)
fromAddHandler :: Frameworks t => AddHandler a -> Moment t (Event t a)
reactimate :: Frameworks t => Event t (IO ()) -> Moment t ()
compile :: (forall t. Frameworks t => Moment t ()) -> IO EventNetwork
actuate :: EventNetwork -> IO ()
Because we use the simplest UI possible — character input/output, we are going to use module Control.Event.Handler, provided by Reactive-banana. Usually the GUI library does this dirty job for us.
A function of type Handler is just an IO action, similar to other IO actions such as getChar or putStrLn (e.g. the latter has type String -> IO ()). A function of type Handler takes a value and performs some IO computation with it. Thus it can only be used inside an IO context (e.g. in main).
From types it's obvious (if you understand basics of monads) that fromAddHandler and reactimate can only be used in Moment context (e.g. makeDescriptionNetwork), while newAddHandler, compile and actuate can only be used in IO context (e.g. main).
You create a pair of values of types AddHandler and Handler using newAddHandler in main, you pass this new AddHandler function to your event-network function, where you can create an event stream out of it using fromAddHandler. You manipulate this event stream as much as you want, then wrap its events in an IO action, and pass the resulting event stream to reactimate.
Filtering events
Now let's only output something, if user presses + or -. Let's output 1 when user presses +, -1 when user presses -. (The rest of the code stays the same).
action :: Char -> Int
action '+' = 1
action '-' = (-1)
action _ = 0
makeNetworkDescription :: Frameworks t => AddHandler Char -> Moment t ()
makeNetworkDescription myAddHandler = do
event <- fromAddHandler myAddHandler
let event' = action <$> filterE (\e -> e=='+' || e=='-') event
reactimate $ putStrLn . show <$> event'
As we don't output if user presses anything beside + or -, the cleaner approach would be:
action :: Char -> Maybe Int
action '+' = Just 1
action '-' = Just (-1)
action _ = Nothing
makeNetworkDescription :: Frameworks t => AddHandler Char -> Moment t ()
makeNetworkDescription myAddHandler = do
event <- fromAddHandler myAddHandler
let event' = filterJust . fmap action $ event
reactimate $ putStrLn . show <$> event'
Important functions for Event manipulations (see Reactive.Banana.Combinators for more):
fmap :: Functor f => (a -> b) -> f a -> f b
union :: Event t a -> Event t a -> Event t a
filterE :: (a -> Bool) -> Event t a -> Event t a
accumE :: a -> Event t (a -> a) -> Event t a
filterJust :: Event t (Maybe a) -> Event t a
Accumulating increments and decrements
But we don't want just to output 1 and -1, we want to increment and decrement the value and remember it between key presses! So we need to accumE. accumE accepts a value and a stream of functions of type (a -> a). Every time a new function appears from this stream, it is applied to the value, and the result is remembered. Next time a new function appears, it is applied to the new value, and so on. This allows us to remember, which number we currently have to decrement or increment.
makeNetworkDescription :: Frameworks t => AddHandler Char -> Moment t ()
makeNetworkDescription myAddHandler = do
event <- fromAddHandler myAddHandler
let event' = filterJust . fmap action $ event
functionStream = (+) <$> event' -- is of type Event t (Int -> Int)
reactimate $ putStrLn . show <$> accumE 0 functionStream
functionStream is basically a stream of functions (+1), (-1), (+1), depending on which key the user pressed.
Uniting two event streams
Now we are ready to implement both octaves and pitch from the original article.
type Octave = Int
type Pitch = Char
actionChangeOctave :: Char -> Maybe Int
actionChangeOctave '+' = Just 1
actionChangeOctave '-' = Just (-1)
actionChangeOctave _ = Nothing
actionPitch :: Char -> Maybe Char
actionPitch c
| c >= 'a' && c <= 'g' = Just c
| otherwise = Nothing
makeNetworkDescription :: Frameworks t => AddHandler Char -> Moment t ()
makeNetworkDescription addKeyEvent = do
event <- fromAddHandler addKeyEvent
let eChangeOctave = filterJust . fmap actionChangeOctave $ event
eOctave = accumE 0 ((+) <$> eChangeOctave)
ePitch = filterJust . fmap actionPitch $ event
eResult = (show <$> ePitch) `union` (show <$> eOctave)
reactimate $ putStrLn <$> eResult
Our program will output either current pitch or current octave, depending on what the user pressed. It will also preserve the value of the current octave. But wait! That's not what we want! What if we want to output both current pitch and current octave, every time user presses either a letter or + or -?
And here it becomes super-hard. We can't union 2 event-streams of different types, so we can convert both of them to Event t (Pitch, Octave). But if a pitch event and an octave event happen at different time (i.e. they are not simultaneous, which is practically certain in our example), then our temporary event-stream would rather have type Event t (Maybe Pitch, Maybe Octave), with Nothing everywhere you haven't a corresponding event. So if a user presses in sequence + b - c +, and we assume that default octave is 0 and default pitch is a, then we end up with a sequence of pairs [(Nothing, Just 1), (Just 'b', Nothing), (Nothing, Just 0), (Just 'c', Nothing), (Nothing, Just 1)], wrapped in Event.
Then we must figure out how to replace Nothing with what would be the current pitch or octave, so the resulting sequence should be something like [('a', 1), ('b', 1), ('b', 0), ('c', 0), ('c', 1)].
This is too low-level and a true programmer shouldn't worry about aligning events like that, when there is a high-level abstraction available.
Behavior simplifies event manipulation
A few simple modifications, and we achieved the same result.
makeNetworkDescription :: Frameworks t => AddHandler Char -> Moment t ()
makeNetworkDescription addKeyEvent = do
event <- fromAddHandler addKeyEvent
let eChangeOctave = filterJust . fmap actionChangeOctave $ event
bOctave = accumB 0 ((+) <$> eChangeOctave)
ePitch = filterJust . fmap actionPitch $ event
bPitch = stepper 'a' ePitch
bResult = (++) <$> (show <$> bPitch) <*> (show <$> bOctave)
eResult <- changes bResult
reactimate' $ (fmap putStrLn) <$> eResult
Turn pitch Event into Behavior with stepper and replace accumE with accumB to get octave Behavior instead of octave Event. To get the resulting Behavior, use applicative style.
Then, to get the event you must pass to reactimate, pass the resulting Behavior to changes. However, changes returns a complicated monadic value Moment t (Event t (Future a)), therefore you should use reactimate' instead of reactimate. This is also the reason, why you have to lift putStrLn in the above example twice into eResult, because you're lifting it to Future functor inside Event functor.
Check out the types of the functions we used here to understand what goes where:
stepper :: a -> Event t a -> Behavior t a
accumB :: a -> Event t (a -> a) -> Behavior t a
changes :: Frameworks t => Behavior t a -> Moment t (Event t (Future a))
reactimate' :: Frameworks t => Event t (Future (IO ())) -> Moment t ()
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.
One idea would be to have an ObjectInput type that gets passed to everything, but that seems bad to me since I could accidentally introduce dependencies I don't want.
On the other hand, I don't know if having a SeekerWire, a PlayerWire, an EnemyWire, etc., would be a good idea since they're almost 'identical' and so I'd have to duplicate functionality across them.
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)
There is a very simple and general solution to this. The key idea is that you never merge sources of different types. Instead, you only merge sources of the same type. The trick that makes this work is that you wrap the output of all your diverse sources in an algebraic data type.
I'm not really familiar with netwire, so if you don't mind I will use pipes as the example. What we want is a merge function that takes a list of sources and combines them into a single source that merges their outputs concurrently, finishing when they all complete. The key type signature is:
merge
:: (Proxy p)
=> [() -> Producer ProxyFast a IO r] -> () -> Producer p a IO ()
That just says that it takes a list of Producers of values of type a, and combines them into a single Producer of values of type a. Here's the implementation of merge, if you are curious and you want to follow along:
import Control.Concurrent
import Control.Concurrent.Chan
import Control.Monad
import Control.Proxy
fromNChan :: (Proxy p) => Int -> Chan (Maybe a) -> () -> Producer p a IO ()
fromNChan n0 chan () = runIdentityP $ loop n0 where
loop 0 = return ()
loop n = do
ma <- lift $ readChan chan
case ma of
Nothing -> loop (n - 1)
Just a -> do
respond a
loop n
toChan :: (Proxy p) => Chan ma -> () -> Consumer p ma IO r
toChan chan () = runIdentityP $ forever $ do
ma <- request ()
lift $ writeChan chan ma
merge
:: (Proxy p)
=> [() -> Producer ProxyFast a IO r] -> () -> Producer p a IO ()
merge producers () = runIdentityP $ do
chan <- lift newChan
lift $ forM_ producers $ \producer -> do
let producer' () = do
(producer >-> mapD Just) ()
respond Nothing
forkIO $ runProxy $ producer' >-> toChan chan
fromNChan (length producers) chan ()
Now, let's imagine that we have two sources of input. The first one generates the integers from 1 to 10 in one second intervals:
throttle :: (Proxy p) => Int -> () -> Pipe p a a IO r
throttle microseconds () = runIdentityP $ forever $ do
a <- request ()
respond a
lift $ threadDelay microseconds
source1 :: (Proxy p) => () -> Producer p Int IO ()
source1 = enumFromS 1 10 >-> throttle 1000000
The second source reads three Strings from user input:
source2 :: (Proxy p) => () -> Producer p String IO ()
source2 = getLineS >-> takeB_ 3
We want to combine these two sources, but their output types don't match, so we define an algebraic data type to unify their outputs into a single type:
data Merge = UserInput String | AutoInt Int deriving Show
Now we can combine them into a single list of identically typed producers by wrapping their outputs in our algebraic data type:
producers :: (Proxy p) => [() -> Producer p Merge IO ()]
producers =
[ source1 >-> mapD UserInput
, source2 >-> mapD AutoInt
]
And we can test it out really quickly:
>>> runProxy $ merge producers >-> printD
AutoInt 1
Test<Enter>
UserInput "Test"
AutoInt 2
AutoInt 3
AutoInt 4
AutoInt 5
Apple<Enter>
UserInput "Apple"
AutoInt 6
AutoInt 7
AutoInt 8
AutoInt 9
AutoInt 10
Banana<Enter>
UserInput "Banana"
>>>
Now you have a combined source. You can then write your game engine to just read from that source, pattern match on the input and then behave appropriately:
engine :: (Proxy p) => () -> Consumer p Merge IO ()
engine () = runIdentityP loop where
loop = do
m <- request ()
case m of
AutoInt n -> do
lift $ putStrLn $ "Generate unit wave #" ++ show n
loop
UserInput str -> case str of
"quit" -> return ()
_ -> loop
Let's try it:
>>> runProxy $ merge producers >-> engine
Generate unit wave #1
Generate unit wave #2
Generate unit wave #3
Test<Enter>
Generate unit wave #4
quit<Enter>
>>>
I imagine the same trick will work for netwire.
Elm has a library for Automatons which I believe is similar to what you are doing.
You could use a typeclass for each type of state you want something to have access to. Then implement each of those classes for the entire state of your game (Assuming you have 1 big fat object holding everything).
-- bfgo = Big fat game object
class HasUserInput bfgo where
mouseState :: bfgo -> MouseState
keyState :: bfgo -> KeyState
class HasPositionState bfgo where
positionState :: bfgo -> [Position] -- Use your data structure
Then when you create the functions for using the data, you simply specify the typeclasses those functions will be using.
{-#LANGUAGE RankNTypes #-}
data Player i = Player
{playerRun :: (HasUserInput i) => (i -> Player i)}
data Projectile i = Projectile
{projectileRun :: (HasPositionState i) => (i -> Projectile i)}