reactivity-0.2.3.0: src/FRP/Reactivity.hs
{-# LANGUAGE Trustworthy, DeriveFunctor, DeriveDataTypeable #-}
-- | A different presentation of functional reactive programming, based on the Reactive
-- library on Hackage. The functionals in Combinators are based on those from Reactive.
module FRP.Reactivity (module Data.Time.Clock.POSIX, Time, Event, firstRestE,
-- * Primitive event combinators (see also Monad and MonadPlus instances)
cons, corec, withTime, withRest, once, over, displace, list, Stream(Stream), addToEvent, getEvent, chanSource,
-- * Executing events
FrameOfReference, startT, setupFrame, makeFrame, runFrame, diagnostic) where
import GHC.Prim (Any)
import Control.Concurrent
import qualified Control.CUtils.FChan as C
import qualified Control.CUtils.Conc as CC
import Control.Monad
import Control.Applicative
import Control.Comonad
import Data.Maybe
import Data.List hiding (union)
import Data.Monoid hiding (Any)
import Data.Function
import Data.Typeable
import qualified Data.Map as M
import Data.List.Extras.Argmax
import Data.Time.Clock.POSIX
import Data.IORef
import System.IO.Unsafe
import Unsafe.Coerce
import System.IO
import System.Mem.Weak
{- Desirable properties of functional reactive systems, and how this system addresses
- them:
-
- * Temporal monotonicity: This is accomplished using a global lock. By forcing
- channel submissions to be ordered, one can ensure a consistent ordering on
- external inputs. Monotonicity is further preserved by the primitive combinators.
- * Glitch-freedom: Once external inputs are acquired, they are preseved in
- channels.
- * Recursion-friendly: Consider the term 'let e = return 1 <> delayE 4 e in e'.
- Without lower bounds this is bottom, but an a priori lower bound makes this
- a productive recursion.
- * Leak-freedom: running a program should take constant space in situations where it
- could in principle take constant space.-}
type Time = Double
data Handler t u = Handler
!(Channel (t, Time))
!(t -> Time -> Event u) deriving Functor
-- | A type of event streams.
data Event t = Event
(Maybe t, Time, Event t)
-- Wait for the first external event in the dictionary. The Time parameter provides a time
-- limit, after which we will continue with the event parameter.
!(M.Map Integer (Handler Any t)) deriving (Typeable, Functor)
pureOccurrences (Event (_, t, _) _) | t == 1/0 = []
pureOccurrences (Event (Nothing, _, e) _) = pureOccurrences e
pureOccurrences (Event (Just x, t, e) _) = (x, t) : pureOccurrences e
instance (Show t) => Show (Event t) where
showsPrec _ e = ("One possible sequence is:"++) . showsPrec 11 (pureOccurrences e)
{-# NOINLINE lock #-}
lock = unsafePerformIO (newMVar ())
-- | Find out if any of the channels contain occurrences.
firstRestE (Event (x, t, rest) mp) = do
when (M.size mp >= 1000) (modifyMVar_ lock $ \_ -> hPutStrLn stderr "Reactivity: Number of event sources getting large (>=1000)")
available <- liftM catMaybes $ mapM (\(Handler (Channel ref) f) -> do
my <- readIORef ref
return (fmap (\((x, t), _) -> (f x t, t)) my)) (M.elems mp)
let (e, t2) = argmin snd available
if null available || t <= t2 then
if t == 1/0 then do
when (M.null mp) (modifyMVar_ lock $ \_ -> hPutStrLn stderr "Reactivity: Event stream is empty!")
return Nothing
else if isJust x then
return (Just (fromJust x, t, rest))
else
firstRestE rest
else
firstRestE e
{-# INLINE cons #-}
cons x t e = Event (Just x, t, displace t e) M.empty
-- | Carry some kind of value which gets updated from occurrence to occurrence, and collect
-- the results in an event.
corec :: (t -> u -> Time -> (t, v, Time)) -> t -> Event u -> Event v
corec f x e = over e (\y t rest -> case f x y t of
(y, z, t2) -> cons z t2 (corec f y rest))
{-# INLINE withTime #-}
-- | Get the time of event occurrences along with their values.
withTime e = corec (\_ x t -> ((), (x, t), t)) () e
-- | This functional lets you get an idea of the future starting from a certain point
-- in time. It is similar to the 'tails' function for lists.
withRest :: Event t -> Event (t, Event t)
withRest e = over e (\x t rest -> cons (x, rest) t (withRest rest))
instance Comonad Event where
duplicate e = over e (\x t rest -> cons (cons x t rest) t (duplicate rest))
extract e = unsafePerformIO $ do
-- Sticky counts
atomicModifyIORef waiting (\x -> (if x == maxBound then x else succ x, ()))
res <- loop e
atomicModifyIORef waiting (\x -> (if x == maxBound then x else pred x, ()))
return res where
loop e = do
my <- firstRestE e
case my of
Just (x, _, _) -> return x
Nothing -> -- There is a semaphore that gets pulsed any time an occurrence
-- enters the system.
waitQSem sem >> loop e
{-# INLINE once #-}
once e = over e (\x t _ -> cons x t mzero)
-- | A case analysis on events.
over :: Event t -> (t -> Time -> Event t -> Event u) -> Event u
over (Event (x, t, rest) mp) f = if isNothing x then
Event (Nothing, t, over rest f) mappedMp
else case f (fromJust x) t rest of
-- Get hold of the first limiting occurrence
Event (y, t2, rest2) mp2 ->
let mappedMp2 = fmap (\(Handler ref g) -> Handler ref (\x t2 -> displace t (g x t2))) mp2 in
-- Get the external alternatives and limit them at 't',
-- Switch in the external alternatives from 'f'; they have to be displaced
-- to beyond 't' for monotonicity.
Event (Nothing, t, Event (y, t`max`t2, rest2) mappedMp2) mappedMp
where
mappedMp = fmap (\(Handler ref g) -> Handler ref (\x t -> over (g x t) f))
mp
displace' :: Time -> Event t -> Event t
displace' t e@(Event (x, t2, rest) mp) = Event (if t <= t2 then
(x, t2, rest)
else
(x, t`max`t2, displace' t rest))
(fmap (\(Handler ref f) -> Handler ref (\x t2 -> if t <= t2 then
f x t2
else
displace' t (f x t2))) mp)
-- | Displace occurrences to at least 't'.
displace t e = Event (Nothing, t, displace' t e) M.empty
-- Starting with a lower bound at 't' helps recursion be productive.
-- | Turns a plain list of occurrences (and times) into an Event.
list ((x, t):xs) = cons x t (list xs)
list [] = mzero
instance MonadPlus Event where
mzero = Event (Nothing, 1/0, mzero) M.empty
mplus (Event (_, t, _) mp) e2 | t == 1/0 && M.null mp = e2
mplus e (Event (_, t, _) mp) | t == 1/0 && M.null mp = e
mplus e@(Event (x, t, rest) mp) e2@(Event (x2, t2, rest2) mp2) = Event
(if t <= t2 then
(x, t, mplus rest e2)
else
(x2, t2, mplus e rest2))
(fmap (\ei -> case ei of
Left ei2 -> case ei2 of
Left (Handler ref f) -> Handler ref (\x t -> mplus (f x t) e2)
Right (Handler ref f) -> Handler ref (\x t -> mplus e (f x t))
Right h -> h)
$ M.unionWith
(\(Left (Left (Handler ref f))) (Left (Right (Handler _ g))) -> Right (Handler ref (\x t -> f x t `mplus` g x t)))
(fmap (Left . Left) mp)
(fmap (Left . Right) mp2))
data Stream t = Stream !(t -> IO ()) !(Event t) deriving Typeable
addToEvent ~(Stream f _) = f
getEvent ~(Stream _ e) = e
{-# NOINLINE counter #-}
counter :: IORef Integer
counter = unsafePerformIO (newIORef 0)
unsafeCast :: Handler t u -> Handler v u
unsafeCast = unsafeCoerce
chanSource :: FrameOfReference -> IO (Stream t)
chanSource frame = do
n <- atomicModifyIORef counter (\x -> (succ x, x))
chn <- liftM Channel $ newIORef Nothing
end <- newIORef chn
let chanLoop chn@(Channel ref) = do
e <- unsafeInterleaveIO (do
Just (_, chn') <- readIORef ref
chanLoop chn')
return (Event (Nothing, 1/0, mzero) (M.singleton n (unsafeCast (Handler chn (\x t -> cons x t e)))))
event <- chanLoop chn
let strm = Stream
(\x -> modifyMVar_ (remainder frame) (\e@(Event tup mp) -> do
-- Lock is for monotonicity
t1 <- getPOSIXTime
let t = fromRational (toRational (t1 - startT frame))
-- Write into the channel
chanWrite end (x, t)
-- ...and deliver directly to the waiting handler
let my = M.lookup n mp
e' <- maybe
(return e)
(\hnd -> do
let Handler _ f = unsafeCast hnd
immediates t (f x t))
my
return e') >> wakeup)
event
mkWeak end end (Just (void $ forkIO $ modifyMVar_ (remainder frame) (\(Event tup mp) -> return (Event tup (M.delete n mp)))))
return strm where
immediates t e = firstRestE e >>=
maybe
(return e)
(\(x, t1, rest) -> if t1 <= t then do
x
immediates t rest
else
return e)
instance Alternative Event where
empty = mzero
(<|>) = mplus
instance Monoid (Event t) where
mempty = mzero
mappend = mplus
-- The monad for Event is much like the list monad:
-- * 'join' - instead of concatenating, it interleaves result events according to their times.
-- * 'unit' - yields a "boring" one-point event at t = 0.
instance Monad Event where
return x = cons x 0 mzero
e >>= f = over e (\x _ rest -> f x <> (rest >>= f))
fail _ = mzero
instance Applicative Event where
pure = return
(<*>) = ap
data Channel t = Channel !(IORef (Maybe (t, Channel t)))
{-# INLINE chanWrite #-}
chanWrite chnEnd x = do
chn' <- newIORef Nothing
Channel chn <- readIORef chnEnd
writeIORef chnEnd (Channel chn')
writeIORef chn (Just (x, Channel chn'))
-------------------------------------------
-- Executing events
data FrameOfReference = FrameOfReference
!(MVar (Event (IO ()))) -- The remainder as of the present time
!POSIXTime -- A start time
deriving Typeable
remainder ~(FrameOfReference mv _) = mv
startT ~(FrameOfReference _ start) = start
{-# NOINLINE waiting #-}
waiting :: IORef Int
waiting = unsafePerformIO (newIORef 0)
-- This is a condition variable that wakes up all waiting threads.
{-# NOINLINE sem #-}
sem = unsafePerformIO (newQSem 0)
{-# INLINE wakeup #-}
wakeup = do
n <- readIORef waiting
modifyMVar_ lock (\_ -> hPutStrLn stderr (show n ++ " bumps"))
replicateM_ n (signalQSem sem)
-- | Create a frame of reference from an event and handler.
{-# INLINE makeFrame #-}
makeFrame e = do
mv <- newMVar e
startT <- getPOSIXTime
return (FrameOfReference mv startT)
{-# INLINE setupFrame #-}
setupFrame frame e = do
tryTakeMVar (remainder frame)
putMVar (remainder frame) e
-- | Run a frame of reference in the current thread -- but N.B. that some executions of the
-- frame's 'sink' may occur in other threads.
--
-- See FRP.Reactivity.Basic for a more elaborate scheme that gets the results
-- from I/O as event occurrences.
runFrame :: FrameOfReference -> IO a
runFrame frame = do
e <- takeMVar (remainder frame)
my <- firstRestE e
let (x, t, rest) = case my of
Just tup -> tup
Nothing -> (undefined, 1/0, mzero)
t1 <- getPOSIXTime
let time = t - fromRational (toRational (t1 - startT frame))
if time <= 0 then do
x
putMVar (remainder frame) rest
else if time == 1/0 then do
atomicModifyIORef waiting (\x -> (if x == maxBound then x else succ x, ()))
putMVar (remainder frame) e
waitQSem sem
atomicModifyIORef waiting (\x -> (if x == maxBound then x else pred x, ()))
else do
putMVar (remainder frame) e
modifyMVar_ lock (\_ -> hPutStrLn stderr ("Wait for " ++ show time))
threadDelay (round (1000000 * time))
runFrame frame
diagnostic (Event (my, t, _) mp) = do
putStr (if isJust my then "occurrence" else "bound")
putStr (" at " ++ show t ++ ", map:")
ls <- mapM (\(n, Handler (Channel ref) _) -> liftM ((,) n . isJust) (readIORef ref)) (M.assocs mp)
print ls