Yampa/reactimate
From HaskellWiki
reactimate :: IO a -- init -> (Bool -> IO (DTime, Maybe a)) -- input/sense -> (Bool -> b -> IO Bool) -- output/actuate -> SF a b -- process/signal function -> IO ()
The Bool parameter of sense and actuate are unused if you look up the definition of reactimate so just ignore them (cf. the explanations below).
reactimate basically is an input-process-output loop and forms the interface between (pure) Yampa signal functions and the (potentially impure) external world. More specifically, a Yampa signal function of type SF a b is an abstract data type that transforms a signal of type Time -> a into a signal of type Time -> b (note that one does not have direct access to signals in Yampa but just to signal functions). The Time parameter here is assumed to model continuous time but to evaluate a signal function (or a signal for that matter) it is necessary to sample the signals at discrete points in time. This is exactly what reactimate does (among other things).
1 Further explanations
- The
initaction is rather self-explanatory; it executes an initial IO action (e.g. print a welcome message), which then yields an initial sample of typeafor the signal function that is passed toreactimateas the last argument. - The
senseargument is then evaluated atFalseand should return an IO action yielding a pair that contains the time passed since the last sample and a new sample of typea(wrapped in aMaybe) for the signal function. If the second component ofsense's return value isNothingthen the previous sample is used again. -
actuateis evaluated atTrueand the signal function's output of typeb, obtained by processing the input sample previously provided bysense.actuate's job now is to process the output (e.g. render a collection of objects contained in it) in an IO action that yields a result of typeBool. If this result isTruethe processing loop stops (i.e. the IO action defined byreactimatereturns()). - Finally, the last argument of
reactimateis the signal function to be run (or "animated"). Keep in mind that the signal function may take pretty complex forms like a parallel switch embedded in a loop.
2 Example
To illustrate this, here's a simple example of a Hello World program but with some time dependence added. Its purpose is to print "Hello... wait for it..." to the console once and then wait for 2 seconds until it prints "World!" and then stops.
{-# LANGUAGE Arrows #-} module Main where import FRP.Animas -- Animas is a fork of Yampa import Data.IORef import System.CPUTime sf :: SF () Bool -- The signal function to be run sf = time >>> arr (\t -> if (t < 2) then False else True) -- the time signal function ignores its input and returns the time main :: IO () main = do t <- getCPUTime -- CPUTime in picoseconds timeRef <- newIORef t let init = putStrLn "Hello... wait for it..." sense = (\_ -> do t' <- getCPUTime t <- readIORef timeRef let dt = fromInteger (t'-t)/10^12 writeIORef timeRef t' return (dt, Nothing)) -- we could equally well return (dt, Just ()) actuate = (\_ x -> if x then putStrLn "World!" >> return x else return x) reactimate init sense actuate sf
Note that as soon as x in the definition of actuate becomes True (that is after 2 seconds), actuate returns True, hence reactimate returns () and the program stops. If we change the definition of actuate to always return False the line "World!" will be print out indefinitely.
