netwire-1.2.7: FRP/NetWire/Wire.hs
-- |
-- Module: FRP.NetWire.Wire
-- Copyright: (c) 2011 Ertugrul Soeylemez
-- License: BSD3
-- Maintainer: Ertugrul Soeylemez <es@ertes.de>
--
-- The module contains the main 'Wire' type and its type class
-- instances. It also provides convenience functions for wire
-- developers.
module FRP.NetWire.Wire
( -- * Wires
Wire(..),
WireState(..),
-- * Auxilliary types
InhibitException(..),
Output,
SF,
Time,
-- * Utilities
cleanupWireState,
inhibitEx,
initWireState,
mkFix,
mkGen,
noEvent,
toGen,
-- * Wire transformers
appEvent,
appFirst,
appFrozen
)
where
import Control.Applicative
import Control.Arrow
import Control.Category
import Control.Concurrent.STM
import Control.Exception (Exception(..), SomeException)
import Control.Monad
import Control.Monad.Fix
import Control.Monad.IO.Class
import Data.Functor.Identity
import Data.Typeable
import Prelude hiding ((.), id)
import System.Random.Mersenne
-- | Inhibition exception with an informative message. This exception
-- is the result of signal inhibition, where no further exception
-- information is available.
data InhibitException =
InhibitException String
deriving (Read, Show, Typeable)
instance Exception InhibitException
-- | Functor for output signals.
type Output = Either SomeException
-- | Signal functions are wires over the identity monad.
type SF = Wire Identity
-- | Time.
type Time = Double
-- | A wire is a network of signal transformers.
data Wire :: (* -> *) -> * -> * -> * where
WArr :: (a -> b) -> Wire m a b
WGen :: (WireState m -> a -> m (Output b, Wire m a b)) -> Wire m a b
-- | This instance corresponds to the 'ArrowPlus' and 'ArrowZero'
-- instances.
instance Monad m => Alternative (Wire m a) where
empty = zeroArrow
(<|>) = (<+>)
-- | Applicative interface to signal networks.
instance Monad m => Applicative (Wire m a) where
pure = arr . const
wf <*> wx = wf &&& wx >>> arr (uncurry ($))
-- | Arrow interface to signal networks.
instance Monad m => Arrow (Wire m) where
arr = WArr
first (WGen f) = WGen $ \ws (x', y) -> liftM (fmap (, y) *** first) (f ws x')
first (WArr f) = WArr (first f)
second (WGen f) = WGen $ \ws (x, y') -> liftM (fmap (x,) *** second) (f ws y')
second (WArr f) = WArr (second f)
(***) = wsidebyside 0
(&&&) = wboth 0
-- | The 'app' combinator has the behaviour of 'appFrozen'. Note that
-- this effectively keeps a wire bound by the "-<<" syntax from
-- evolving. For alternative embedding combinators see also 'appEvent'
-- and 'appFirst'.
instance Monad m => ArrowApply (Wire m) where
app = appFrozen
-- | Signal routing. Unused routes are ignored. Note that they still
-- run in real time, i.e. the time deltas passed are accumulated.
instance Monad m => ArrowChoice (Wire m) where
left w' = wl 0
where
wl t' =
WGen $ \ws@(wsDTime -> dt) mx' ->
let t = t' + dt in
t `seq`
case mx' of
Left x' -> liftM (fmap Left *** left) (toGen w' (ws { wsDTime = t }) x')
Right x -> return (pure (Right x), wl t)
right w' = wl 0
where
wl t' =
WGen $ \ws@(wsDTime -> dt) mx' ->
let t = t' + dt in
t `seq`
case mx' of
Right x' -> liftM (fmap Right *** right) (toGen w' (ws { wsDTime = t }) x')
Left x -> return (pure (Left x), wl t)
wf' +++ wg' = wl 0 0 wf' wg'
where
wl tf' tg' wf' wg' =
WGen $ \ws@(wsDTime -> dt) mx' ->
let tf = tf' + dt
tg = tg' + dt in
tf `seq` tg `seq`
case mx' of
Left x' -> do
(mx, wf) <- toGen wf' (ws { wsDTime = tf }) x'
return (fmap Left mx, wl 0 tg wf wg')
Right x' -> do
(mx, wg) <- toGen wg' (ws { wsDTime = tg }) x'
return (fmap Right mx, wl tf 0 wf' wg)
wf' ||| wg' = wl 0 0 wf' wg'
where
wl tf' tg' wf' wg' =
WGen $ \ws@(wsDTime -> dt) mx' ->
let tf = tf' + dt
tg = tg' + dt in
tf `seq` tg `seq`
case mx' of
Left x' -> do
(mx, wf) <- toGen wf' (ws { wsDTime = tf }) x'
return (mx, wl 0 tg wf wg')
Right x' -> do
(mx, wg) <- toGen wg' (ws { wsDTime = tg }) x'
return (mx, wl tf 0 wf' wg)
-- | Value recursion. Warning: Recursive signal networks must never
-- inhibit. Make use of 'FRP.NetWire.Tools.exhibit' or
-- 'FRP.NetWire.Event.event' for wires that may inhibit.
instance MonadFix m => ArrowLoop (Wire m) where
loop w' =
WGen $ \ws x' -> do
rec (Right (x, d), w) <- toGen w' ws (x', d)
return (Right x, loop w)
-- | Left-biased signal network combination. If the left arrow
-- inhibits, the right arrow is tried. If both inhibit, their
-- combination inhibits. Ignored wire networks still run in real time,
-- i.e. passed time deltas are accumulated.
instance Monad m => ArrowPlus (Wire m) where
wf'@(WGen _) <+> wg' = wl 0 wf' wg'
where
wl t' wf' wg' =
WGen $ \ws@(wsDTime -> dt) x' -> do
let t = t' + dt
(mx, wf) <- toGen wf' ws x'
case mx of
Right _ -> t `seq` return (mx, wl t wf wg')
Left _ -> do
(mx2, wg) <- t `seq` toGen wg' (ws { wsDTime = t }) x'
return (mx2, wl 0 wf wg)
wa@(WArr _) <+> _ = wa
-- | The zero arrow always inhibits.
instance Monad m => ArrowZero (Wire m) where
zeroArrow = mkFix $ \_ _ -> return (Left (inhibitEx "Signal inhibited"))
-- | Identity signal network and signal network sequencing.
instance Monad m => Category (Wire m) where
id = WArr id
(.) = flip (wcompose 0)
-- | Map over the output of a signal network.
instance Monad m => Functor (Wire m a) where
fmap f = (>>> arr f)
-- | The state of the wire.
data WireState :: (* -> *) -> * where
ImpureState ::
MonadIO m =>
{ wsDTime :: Double, -- ^ Time difference for current instant.
wsRndGen :: MTGen, -- ^ Random number generator.
wsReqVar :: TVar Int -- ^ Request counter.
} -> WireState m
PureState :: { wsDTime :: Double } -> WireState m
-- | Embeds the input wire (left signal) into the network with the given
-- input signal (right signal). Each time the input wire is a 'Just',
-- the current state of the last wire is discarded and the new wire is
-- evolved instead. New wires can be generated by an event wire and
-- catched via 'FRP.NetWire.Event.event'. The initial wire is given by
-- the argument.
--
-- Inhibits whenever the embedded wire inhibits. Same feedback
-- behaviour as the embedded wire.
appEvent ::
forall a b m. Monad m
=> Wire m a b
-> Wire m (Maybe (Wire m a b), a) b
appEvent cw' =
mkGen $ \ws (mw, x') -> do
let w' = maybe cw' id mw
(mx, w) <- toGen w' ws x'
return (mx, appEvent w)
-- | Embeds the first received input wire (left signal) into the
-- network, feeding it the right signal. This wire respects its left
-- signal only in the first instant, after which it wraps that wire's
-- evolution.
--
-- Inhibits whenever the embedded wire inhibits. Same feedback
-- behaviour as the embedded wire.
appFirst :: forall a b m. Monad m => Wire m (Wire m a b, a) b
appFirst =
mkGen $ \ws (w', x') -> do
(mx, w) <- toGen w' ws x'
return (mx, embed w)
where
embed :: Wire m a b -> Wire m (Wire m a b, a) b
embed w' =
mkGen $ \ws (_, x') -> do
(mx, w) <- toGen w' ws x'
return (mx, embed w)
-- | Embeds the first instant of the input wire (left signal) into the
-- network, feeding it the right signal. This wire respects its left
-- signal in all instances, such that the embedded wire cannot evolve.
--
-- Inhibits whenever the embedded wire inhibits. Same feedback
-- behaviour as the embedded wire.
appFrozen :: Monad m => Wire m (Wire m a b, a) b
appFrozen = mkFix $ \ws (w, x') -> liftM fst (toGen w ws x')
-- | Clean up wire state.
cleanupWireState :: WireState m -> IO ()
cleanupWireState _ = return ()
-- | Construct an 'InhibitException' wrapped in a 'SomeException'.
inhibitEx :: String -> SomeException
inhibitEx = toException . InhibitException
-- | Initialize wire state.
initWireState :: MonadIO m => IO (WireState m)
initWireState =
ImpureState
<$> pure 0
<*> getStdGen
<*> newTVarIO 0
-- | Create a fixed wire from the given function. This is a smart
-- constructor. It creates a stateless wire.
mkFix :: Monad m => (WireState m -> a -> m (Output b)) -> Wire m a b
mkFix f = let w = WGen $ \ws -> liftM (, w) . f ws in w
-- | Create a generic (i.e. possibly stateful) wire from the given
-- function. This is a smart constructor. Please use it instead of the
-- 'WGen' constructor for creating generic wires.
mkGen :: (WireState m -> a -> m (Output b, Wire m a b)) -> Wire m a b
mkGen = WGen
-- | Construct an 'InhibitException' wrapped in a 'SomeException' with a
-- message indicating that a certain event did not happen.
noEvent :: SomeException
noEvent = inhibitEx "No event"
-- | Extract the transition function of a wire. Unless there is reason
-- (like optimization) to pattern-match against the 'Wire' constructors,
-- this function is the recommended way to evolve a wire.
toGen :: Monad m => Wire m a b -> WireState m -> a -> m (Output b, Wire m a b)
toGen (WGen f) ws x = f ws x
toGen wf@(WArr f) _ x = return (Right (f x), wf)
-- | Efficient signal sharing.
wboth :: Monad m => Time -> Wire m a b -> Wire m a c -> Wire m a (b, c)
wboth t' (WGen f) wg'@(WGen g) =
WGen $ \ws@(wsDTime -> dt) x' -> do
let t = t' + dt
(mx1, wf) <- t `seq` f ws x'
case mx1 of
Left ex -> return (Left ex, wboth t wf wg')
Right _ -> do
(mx2, wg) <- g ws x'
return (liftA2 (,) mx1 mx2, wboth 0 wf wg)
wboth t' wf@(WArr f) (WGen g) =
WGen $ \ws x' -> do
(mx2, wg) <- g ws x'
return (fmap (f x',) mx2, wboth t' wf wg)
wboth t' (WGen f) wg@(WArr g) =
WGen $ \ws x' -> do
(mx1, wf) <- f ws x'
return (fmap (, g x') mx1, wboth t' wf wg)
wboth _ (WArr f) (WArr g) = WArr (f &&& g)
-- | Efficient forward-composition of two wires.
wcompose :: Monad m => Time -> Wire m a b -> Wire m b c -> Wire m a c
wcompose t' (WGen f) wg'@(WGen g) =
WGen $ \ws@(wsDTime -> dt) x'' -> do
let t = t' + dt
(mx', wf) <- t `seq` f ws x''
case mx' of
Left ex -> return (Left ex, wcompose t wf wg')
Right x' -> do
(mx, wg) <- g (ws { wsDTime = t }) x'
return (mx, wcompose 0 wf wg)
wcompose t' wf@(WArr f) (WGen g) =
WGen $ \ws x' -> do
(mx, wg) <- g ws (f x')
return (mx, wcompose t' wf wg)
wcompose t' (WGen f) wg@(WArr g) =
WGen $ \ws x' -> do
(mx, wf) <- f ws x'
return (fmap g mx, wcompose t' wf wg)
wcompose _ (WArr f) (WArr g) = WArr (g . f)
-- | Run two signals through two signal networks.
wsidebyside :: Monad m => Time -> Wire m a c -> Wire m b d -> Wire m (a, b) (c, d)
wsidebyside t' (WGen f) wg'@(WGen g) =
WGen $ \ws@(wsDTime -> dt) (x', y') -> do
let t = t' + dt
(mx, wf) <- t `seq` f ws x'
case mx of
Left ex -> return (Left ex, wsidebyside t wf wg')
Right _ -> do
(my, wg) <- g ws y'
return (liftA2 (,) mx my, wsidebyside 0 wf wg)
wsidebyside t' wf@(WArr f) (WGen g) =
WGen $ \ws (x', y') -> do
(my, wg) <- g ws y'
return (fmap (f x',) my, wsidebyside t' wf wg)
wsidebyside t' (WGen f) wg@(WArr g) =
WGen $ \ws (x', y') -> do
(mx, wf) <- f ws x'
return (fmap (, g y') mx, wsidebyside t' wf wg)
wsidebyside _ (WArr f) (WArr g) = WArr (f *** g)