packages feed

elerea 2.4.0 → 2.5.0

raw patch · 9 files changed

+368/−1420 lines, 9 filesdep −mersenne-randomPVP ok

version bump matches the API change (PVP)

Dependencies removed: mersenne-random

API changes (from Hackage documentation)

- FRP.Elerea.Clocked: debug :: String -> SignalGen ()
- FRP.Elerea.Clocked: getRandom :: MTRandom a => SignalGen a
- FRP.Elerea.Clocked: noise :: MTRandom a => SignalGen (Signal a)
- FRP.Elerea.Legacy: (&&@) :: Signal Bool -> Signal Bool -> Signal Bool
- FRP.Elerea.Legacy: (.@.) :: Signal a -> Signal t -> Signal a
- FRP.Elerea.Legacy: (/=@) :: Eq a => Signal a -> Signal a -> Signal Bool
- FRP.Elerea.Legacy: (<=@) :: Ord a => Signal a -> Signal a -> Signal Bool
- FRP.Elerea.Legacy: (<@) :: Ord a => Signal a -> Signal a -> Signal Bool
- FRP.Elerea.Legacy: (==@) :: Eq a => Signal a -> Signal a -> Signal Bool
- FRP.Elerea.Legacy: (>=@) :: Ord a => Signal a -> Signal a -> Signal Bool
- FRP.Elerea.Legacy: (>@) :: Ord a => Signal a -> Signal a -> Signal Bool
- FRP.Elerea.Legacy: (||@) :: Signal Bool -> Signal Bool -> Signal Bool
- FRP.Elerea.Legacy: createSignal :: SignalMonad a -> IO a
- FRP.Elerea.Legacy: data Signal a
- FRP.Elerea.Legacy: data SignalMonad a
- FRP.Elerea.Legacy: delay :: a -> Signal a -> SignalMonad (Signal a)
- FRP.Elerea.Legacy: edge :: Signal Bool -> SignalMonad (Signal Bool)
- FRP.Elerea.Legacy: external :: a -> IO (Signal a, Sink a)
- FRP.Elerea.Legacy: generator :: Signal Bool -> Signal (SignalMonad a) -> Signal (Maybe a)
- FRP.Elerea.Legacy: keepAlive :: Signal a -> Signal t -> Signal a
- FRP.Elerea.Legacy: sampler :: Signal (Signal a) -> Signal a
- FRP.Elerea.Legacy: signalDebug :: Show a => a -> SignalMonad ()
- FRP.Elerea.Legacy: stateful :: a -> (DTime -> a -> a) -> SignalMonad (Signal a)
- FRP.Elerea.Legacy: storeJust :: a -> Signal (Maybe a) -> SignalMonad (Signal a)
- FRP.Elerea.Legacy: superstep :: Signal a -> DTime -> IO a
- FRP.Elerea.Legacy: toMaybe :: Bool -> a -> Maybe a
- FRP.Elerea.Legacy: transfer :: a -> (DTime -> t -> a -> a) -> Signal t -> SignalMonad (Signal a)
- FRP.Elerea.Legacy: type DTime = Double
- FRP.Elerea.Legacy: type Sink a = a -> IO ()
- FRP.Elerea.Legacy.Delayed: data Signal p a
- FRP.Elerea.Legacy.Delayed: data SignalGen p a
- FRP.Elerea.Legacy.Delayed: debug :: String -> SignalGen p ()
- FRP.Elerea.Legacy.Delayed: delay :: a -> Signal p a -> SignalGen p (Signal p a)
- FRP.Elerea.Legacy.Delayed: external :: a -> IO (Signal p a, a -> IO ())
- FRP.Elerea.Legacy.Delayed: externalMulti :: IO (SignalGen p (Signal p [a]), a -> IO ())
- FRP.Elerea.Legacy.Delayed: generator :: Signal p (SignalGen p a) -> SignalGen p (Signal p a)
- FRP.Elerea.Legacy.Delayed: getRandom :: MTRandom a => SignalGen p a
- FRP.Elerea.Legacy.Delayed: instance Applicative (Signal p)
- FRP.Elerea.Legacy.Delayed: instance Applicative (SignalGen p)
- FRP.Elerea.Legacy.Delayed: instance Bounded t => Bounded (Signal p t)
- FRP.Elerea.Legacy.Delayed: instance Enum t => Enum (Signal p t)
- FRP.Elerea.Legacy.Delayed: instance Eq (Signal p a)
- FRP.Elerea.Legacy.Delayed: instance Floating t => Floating (Signal p t)
- FRP.Elerea.Legacy.Delayed: instance Fractional t => Fractional (Signal p t)
- FRP.Elerea.Legacy.Delayed: instance Functor (Signal p)
- FRP.Elerea.Legacy.Delayed: instance Functor (SignalGen p)
- FRP.Elerea.Legacy.Delayed: instance Integral t => Integral (Signal p t)
- FRP.Elerea.Legacy.Delayed: instance Monad (Signal p)
- FRP.Elerea.Legacy.Delayed: instance Monad (SignalGen p)
- FRP.Elerea.Legacy.Delayed: instance MonadFix (SignalGen p)
- FRP.Elerea.Legacy.Delayed: instance Num t => Num (Signal p t)
- FRP.Elerea.Legacy.Delayed: instance Ord t => Ord (Signal p t)
- FRP.Elerea.Legacy.Delayed: instance Real t => Real (Signal p t)
- FRP.Elerea.Legacy.Delayed: instance Show (Signal p a)
- FRP.Elerea.Legacy.Delayed: memo :: Signal p a -> SignalGen p (Signal p a)
- FRP.Elerea.Legacy.Delayed: noise :: MTRandom a => SignalGen p (Signal p a)
- FRP.Elerea.Legacy.Delayed: start :: SignalGen p (Signal p a) -> IO (p -> IO a)
- FRP.Elerea.Legacy.Delayed: stateful :: a -> (p -> a -> a) -> SignalGen p (Signal p a)
- FRP.Elerea.Legacy.Delayed: transfer :: a -> (p -> t -> a -> a) -> Signal p t -> SignalGen p (Signal p a)
- FRP.Elerea.Legacy.Graph: signalToDot :: Signal a -> IO String
- FRP.Elerea.Legacy.Internal: Aged :: a -> (SignalNode a) -> SignalTrans a
- FRP.Elerea.Legacy.Internal: Ready :: (SignalNode a) -> SignalTrans a
- FRP.Elerea.Legacy.Internal: S :: (IORef (SignalTrans a)) -> Signal a
- FRP.Elerea.Legacy.Internal: SM :: IO a -> SignalMonad a
- FRP.Elerea.Legacy.Internal: SNA :: (Signal (t -> a)) -> (Signal t) -> SignalNode a
- FRP.Elerea.Legacy.Internal: SND :: a -> (Signal a) -> SignalNode a
- FRP.Elerea.Legacy.Internal: SNE :: (IORef a) -> SignalNode a
- FRP.Elerea.Legacy.Internal: SNF1 :: (t -> a) -> (Signal t) -> SignalNode a
- FRP.Elerea.Legacy.Internal: SNF2 :: (t1 -> t2 -> a) -> (Signal t1) -> (Signal t2) -> SignalNode a
- FRP.Elerea.Legacy.Internal: SNF3 :: (t1 -> t2 -> t3 -> a) -> (Signal t1) -> (Signal t2) -> (Signal t3) -> SignalNode a
- FRP.Elerea.Legacy.Internal: SNF4 :: (t1 -> t2 -> t3 -> t4 -> a) -> (Signal t1) -> (Signal t2) -> (Signal t3) -> (Signal t4) -> SignalNode a
- FRP.Elerea.Legacy.Internal: SNF5 :: (t1 -> t2 -> t3 -> t4 -> t5 -> a) -> (Signal t1) -> (Signal t2) -> (Signal t3) -> (Signal t4) -> (Signal t5) -> SignalNode a
- FRP.Elerea.Legacy.Internal: SNH :: (Signal (Signal a)) -> (IORef (Signal a)) -> SignalNode a
- FRP.Elerea.Legacy.Internal: SNK :: a -> SignalNode a
- FRP.Elerea.Legacy.Internal: SNKA :: (Signal a) -> (Signal t) -> SignalNode a
- FRP.Elerea.Legacy.Internal: SNM :: (Signal Bool) -> (Signal (SignalMonad a)) -> SignalNode a
- FRP.Elerea.Legacy.Internal: SNS :: a -> (DTime -> a -> a) -> SignalNode a
- FRP.Elerea.Legacy.Internal: SNT :: (Signal t) -> a -> (DTime -> t -> a -> a) -> SignalNode a
- FRP.Elerea.Legacy.Internal: Sampled :: a -> (SignalNode a) -> SignalTrans a
- FRP.Elerea.Legacy.Internal: Sampling :: (SignalNode a) -> SignalTrans a
- FRP.Elerea.Legacy.Internal: advance :: SignalNode a -> a -> DTime -> IO (SignalNode a)
- FRP.Elerea.Legacy.Internal: age :: Signal a -> DTime -> IO ()
- FRP.Elerea.Legacy.Internal: commit :: Signal a -> IO ()
- FRP.Elerea.Legacy.Internal: createSignal :: SignalMonad a -> IO a
- FRP.Elerea.Legacy.Internal: data SignalNode a
- FRP.Elerea.Legacy.Internal: data SignalTrans a
- FRP.Elerea.Legacy.Internal: debugLog :: String -> IO a -> IO a
- FRP.Elerea.Legacy.Internal: delay :: a -> Signal a -> SignalMonad (Signal a)
- FRP.Elerea.Legacy.Internal: external :: a -> IO (Signal a, Sink a)
- FRP.Elerea.Legacy.Internal: generator :: Signal Bool -> Signal (SignalMonad a) -> Signal (Maybe a)
- FRP.Elerea.Legacy.Internal: instance Applicative Signal
- FRP.Elerea.Legacy.Internal: instance Applicative SignalMonad
- FRP.Elerea.Legacy.Internal: instance Bounded t => Bounded (Signal t)
- FRP.Elerea.Legacy.Internal: instance Enum t => Enum (Signal t)
- FRP.Elerea.Legacy.Internal: instance Eq (Signal a)
- FRP.Elerea.Legacy.Internal: instance Floating t => Floating (Signal t)
- FRP.Elerea.Legacy.Internal: instance Fractional t => Fractional (Signal t)
- FRP.Elerea.Legacy.Internal: instance Functor Signal
- FRP.Elerea.Legacy.Internal: instance Functor SignalMonad
- FRP.Elerea.Legacy.Internal: instance Integral t => Integral (Signal t)
- FRP.Elerea.Legacy.Internal: instance Monad SignalMonad
- FRP.Elerea.Legacy.Internal: instance MonadFix SignalMonad
- FRP.Elerea.Legacy.Internal: instance Num t => Num (Signal t)
- FRP.Elerea.Legacy.Internal: instance Ord t => Ord (Signal t)
- FRP.Elerea.Legacy.Internal: instance Real t => Real (Signal t)
- FRP.Elerea.Legacy.Internal: instance Show (Signal a)
- FRP.Elerea.Legacy.Internal: keepAlive :: Signal a -> Signal t -> Signal a
- FRP.Elerea.Legacy.Internal: makeSignal :: SignalNode a -> SignalMonad (Signal a)
- FRP.Elerea.Legacy.Internal: makeSignalUnsafe :: SignalNode a -> Signal a
- FRP.Elerea.Legacy.Internal: newtype Signal a
- FRP.Elerea.Legacy.Internal: newtype SignalMonad a
- FRP.Elerea.Legacy.Internal: sample :: SignalNode a -> DTime -> IO a
- FRP.Elerea.Legacy.Internal: sampleDelayed :: SignalNode a -> DTime -> IO a
- FRP.Elerea.Legacy.Internal: sampler :: Signal (Signal a) -> Signal a
- FRP.Elerea.Legacy.Internal: signalDebug :: Show a => a -> SignalMonad ()
- FRP.Elerea.Legacy.Internal: signalValue :: Signal a -> DTime -> IO a
- FRP.Elerea.Legacy.Internal: stateful :: a -> (DTime -> a -> a) -> SignalMonad (Signal a)
- FRP.Elerea.Legacy.Internal: superstep :: Signal a -> DTime -> IO a
- FRP.Elerea.Legacy.Internal: toMaybe :: Bool -> a -> Maybe a
- FRP.Elerea.Legacy.Internal: transfer :: a -> (DTime -> t -> a -> a) -> Signal t -> SignalMonad (Signal a)
- FRP.Elerea.Legacy.Internal: type DTime = Double
- FRP.Elerea.Legacy.Internal: type Sink a = a -> IO ()
- FRP.Elerea.Legacy.Internal: unimp :: String -> a
- FRP.Elerea.Param: debug :: String -> SignalGen p ()
- FRP.Elerea.Param: getRandom :: MTRandom a => SignalGen p a
- FRP.Elerea.Param: noise :: MTRandom a => SignalGen p (Signal a)
- FRP.Elerea.Simple: debug :: String -> SignalGen ()
- FRP.Elerea.Simple: getRandom :: MTRandom a => SignalGen a
- FRP.Elerea.Simple: noise :: MTRandom a => SignalGen (Signal a)
+ FRP.Elerea.Clocked: effectful :: IO a -> SignalGen (Signal a)
+ FRP.Elerea.Clocked: effectful1 :: (t -> IO a) -> Signal t -> SignalGen (Signal a)
+ FRP.Elerea.Clocked: effectful2 :: (t1 -> t2 -> IO a) -> Signal t1 -> Signal t2 -> SignalGen (Signal a)
+ FRP.Elerea.Clocked: effectful3 :: (t1 -> t2 -> t3 -> IO a) -> Signal t1 -> Signal t2 -> Signal t3 -> SignalGen (Signal a)
+ FRP.Elerea.Clocked: effectful4 :: (t1 -> t2 -> t3 -> t4 -> IO a) -> Signal t1 -> Signal t2 -> Signal t3 -> Signal t4 -> SignalGen (Signal a)
+ FRP.Elerea.Clocked: execute :: IO a -> SignalGen a
+ FRP.Elerea.Param: effectful :: IO a -> SignalGen p (Signal a)
+ FRP.Elerea.Param: effectful1 :: (t -> IO a) -> Signal t -> SignalGen p (Signal a)
+ FRP.Elerea.Param: effectful2 :: (t1 -> t2 -> IO a) -> Signal t1 -> Signal t2 -> SignalGen p (Signal a)
+ FRP.Elerea.Param: effectful3 :: (t1 -> t2 -> t3 -> IO a) -> Signal t1 -> Signal t2 -> Signal t3 -> SignalGen p (Signal a)
+ FRP.Elerea.Param: effectful4 :: (t1 -> t2 -> t3 -> t4 -> IO a) -> Signal t1 -> Signal t2 -> Signal t3 -> Signal t4 -> SignalGen p (Signal a)
+ FRP.Elerea.Param: execute :: IO a -> SignalGen p a
+ FRP.Elerea.Simple: execute :: IO a -> SignalGen a
- FRP.Elerea.Simple: effectful :: IO (IO a) -> SignalGen (Signal a)
+ FRP.Elerea.Simple: effectful :: IO a -> SignalGen (Signal a)
- FRP.Elerea.Simple: effectful1 :: IO (t -> IO a) -> Signal t -> SignalGen (Signal a)
+ FRP.Elerea.Simple: effectful1 :: (t -> IO a) -> Signal t -> SignalGen (Signal a)
- FRP.Elerea.Simple: effectful2 :: IO (t1 -> t2 -> IO a) -> Signal t1 -> Signal t2 -> SignalGen (Signal a)
+ FRP.Elerea.Simple: effectful2 :: (t1 -> t2 -> IO a) -> Signal t1 -> Signal t2 -> SignalGen (Signal a)
- FRP.Elerea.Simple: effectful3 :: IO (t1 -> t2 -> t3 -> IO a) -> Signal t1 -> Signal t2 -> Signal t3 -> SignalGen (Signal a)
+ FRP.Elerea.Simple: effectful3 :: (t1 -> t2 -> t3 -> IO a) -> Signal t1 -> Signal t2 -> Signal t3 -> SignalGen (Signal a)
- FRP.Elerea.Simple: effectful4 :: IO (t1 -> t2 -> t3 -> t4 -> IO a) -> Signal t1 -> Signal t2 -> Signal t3 -> Signal t4 -> SignalGen (Signal a)
+ FRP.Elerea.Simple: effectful4 :: (t1 -> t2 -> t3 -> t4 -> IO a) -> Signal t1 -> Signal t2 -> Signal t3 -> Signal t4 -> SignalGen (Signal a)

Files

CHANGES view
@@ -1,3 +1,12 @@+2.5.0 - 111122+* added SignalGen liftIO equivalent to assist library writers+* simplified the signatures of effectful* combinators+* updated Param to use the more modern codebase (like Simple and+  Clocked); this was necessary to support effectful signals+* temporarily removed the static optimisation from Param+* removed dependency on mersenne-random+* removed legacy branch+ 2.4.0 - 111111 * added effectful signals to assist library writers 
FRP/Elerea/Clocked.hs view
@@ -71,7 +71,6 @@     , start     , external     , externalMulti-    , debug     -- * Basic building blocks     , delay     , generator@@ -85,9 +84,14 @@     , transfer2     , transfer3     , transfer4-    -- * Random sources-    , noise-    , getRandom+    -- * Signals with side effects+    -- $effectful+    , execute+    , effectful+    , effectful1+    , effectful2+    , effectful3+    , effectful4     ) where  import Control.Applicative@@ -98,7 +102,6 @@ import Data.Maybe import Prelude hiding (until) import System.Mem.Weak-import System.Random.Mersenne  -- | A signal represents a value changing over time.  It can be -- thought of as a function of type @Nat -> a@, where the argument is@@ -160,7 +163,7 @@ data Phase a = Ready a | Updated a a  instance Functor SignalGen where-    fmap = (<*>).pure+    fmap = liftM  instance Applicative SignalGen where     pure = return@@ -171,7 +174,7 @@     SG g >>= f = SG $ \p1 p2 -> g p1 p2 >>= \x -> unSG (f x) p1 p2  instance MonadFix SignalGen where-    mfix f = SG $ \p1 p2 -> mfix (\x -> unSG (f x) p1 p2)+    mfix f = SG $ \p1 p2 -> mfix $ \x -> unSG (f x) p1 p2  getUpdate :: Update -> IO (Maybe (Update, UpdateAction)) getUpdate upd@(USig ptr) = (fmap.fmap) ((,) upd) (deRefWeak ptr)@@ -280,6 +283,10 @@      addSignal return update ref pool +-- | Auxiliary function.+memoise :: IORef (Phase a) -> a -> IO a+memoise ref x = writeIORef ref (Updated undefined x) >> return x+ -- | A reactive signal that takes the value to output from a signal -- generator carried by its input with the sampling time provided as -- the start time for the generated structure.  It is possible to@@ -314,11 +321,7 @@ generator (S s) = SG $ \gpool pool -> do     ref <- newIORef (Ready undefined) -    let sample = do-            SG g <- s-            x <- g gpool pool-            writeIORef ref (Updated undefined x)-            return x+    let sample = (s >>= \(SG g) -> g gpool pool) >>= memoise ref      addSignal (const sample) (const (() <$ sample)) ref gpool @@ -344,7 +347,7 @@ memo (S s) = SG $ \_gpool pool -> do     ref <- newIORef (Ready undefined) -    let sample = s >>= \x -> writeIORef ref (Updated undefined x) >> return x+    let sample = s >>= memoise ref      addSignal (const sample) (const (() <$ sample)) ref pool @@ -596,28 +599,116 @@     sig' <- delay x0 sig     memo (liftM5 f s1 s2 s3 s4 sig') --- | A random signal.  It is affected by the associated clock.+{- $effectful++The following combinators are primarily aimed at library implementors+who wish build abstractions to effectful libraries on top of Elerea.++-}++-- | An IO action executed in the 'SignalGen' monad. Can be used as+-- `liftIO`.+execute :: IO a -> SignalGen a+execute act = SG $ \_ _ -> act++-- | A signal that executes a given IO action once at every sampling. --+-- In essence, this combinator provides cooperative multitasking+-- capabilities, and its primary purpose is to assist library writers+-- in wrapping effectful APIs as conceptually pure signals.  If there+-- are several effectful signals in the system, their order of+-- execution is undefined and should not be relied on.+-- -- Example: -- -- > do--- >     smp <- start noise :: IO (IO Double)+-- >     smp <- start $ do+-- >         ref <- execute $ newIORef 0+-- >         effectful $ do+-- >             x <- readIORef ref+-- >             putStrLn $ "Count: " ++ show x+-- >             writeIORef ref $! x+1+-- >             return ()+-- >     replicateM_ 5 smp+--+-- Output:+--+-- > Count: 0+-- > Count: 1+-- > Count: 2+-- > Count: 3+-- > Count: 4+--+-- Another example (requires mersenne-random):+--+-- > do+-- >     smp <- start $ effectful $ return randomIO :: IO (IO Double) -- >     res <- replicateM 5 smp -- >     print res -- -- Output: -- -- > [0.12067753390401374,0.8658877349182655,0.7159264443196786,0.1756941896012891,0.9513646060896676]-noise :: MTRandom a => SignalGen (Signal a)-noise = memo (S randomIO)+effectful :: IO a                 -- ^ the action to be executed repeatedly+          -> SignalGen (Signal a)+effectful act = SG $ \_gpool pool -> do+  ref <- newIORef (Ready undefined) --- | A random source within the 'SignalGen' monad.-getRandom :: MTRandom a => SignalGen a-getRandom = SG $ \_ _ -> randomIO+  let sample = act >>= memoise ref --- | A printing action within the 'SignalGen' monad.-debug :: String -> SignalGen ()-debug s = SG $ \_ _ -> putStrLn s+  addSignal (const sample) (const (() <$ sample)) ref pool++-- | A signal that executes a parametric IO action once at every+-- sampling.  The parameter is supplied by another signal at every+-- sampling step.+effectful1 :: (t -> IO a)          -- ^ the action to be executed repeatedly+           -> Signal t             -- ^ parameter signal+           -> SignalGen (Signal a)+effectful1 act (S s) = SG $ \_gpool pool -> do+  ref <- newIORef (Ready undefined)++  let sample = s >>= act >>= memoise ref++  addSignal (const sample) (const (() <$ sample)) ref pool++-- | Like 'effectful1', but with two parameter signals.+effectful2 :: (t1 -> t2 -> IO a)   -- ^ the action to be executed repeatedly+           -> Signal t1            -- ^ parameter signal 1+           -> Signal t2            -- ^ parameter signal 2+           -> SignalGen (Signal a)+effectful2 act (S s1) (S s2) = SG $ \_gpool pool -> do+  ref <- newIORef (Ready undefined)++  let sample = join (liftM2 act s1 s2) >>= memoise ref++  addSignal (const sample) (const (() <$ sample)) ref pool++-- | Like 'effectful1', but with three parameter signals.+effectful3 :: (t1 -> t2 -> t3 -> IO a) -- ^ the action to be executed repeatedly+           -> Signal t1                -- ^ parameter signal 1+           -> Signal t2                -- ^ parameter signal 2+           -> Signal t3                -- ^ parameter signal 3+           -> SignalGen (Signal a)+effectful3 act (S s1) (S s2) (S s3) = SG $ \_gpool pool -> do+  ref <- newIORef (Ready undefined)++  let sample = join (liftM3 act s1 s2 s3) >>= memoise ref++  addSignal (const sample) (const (() <$ sample)) ref pool++-- | Like 'effectful1', but with four parameter signals.+effectful4 :: (t1 -> t2 -> t3 -> t4 -> IO a) -- ^ the action to be executed repeatedly+           -> Signal t1                      -- ^ parameter signal 1+           -> Signal t2                      -- ^ parameter signal 2+           -> Signal t3                      -- ^ parameter signal 3+           -> Signal t4                      -- ^ parameter signal 4+           -> SignalGen (Signal a)+effectful4 act (S s1) (S s2) (S s3) (S s4) = SG $ \_gpool pool -> do+  ref <- newIORef (Ready undefined)++  let sample = join (liftM4 act s1 s2 s3 s4) >>= memoise ref++  addSignal (const sample) (const (() <$ sample)) ref pool  instance Show (Signal a) where     showsPrec _ _ s = "<SIGNAL>" ++ s
− FRP/Elerea/Legacy.hs
@@ -1,121 +0,0 @@-{-|--Elerea (Eventless Reactivity) is a simplistic FRP implementation that-parts with the concept of events, and introduces various constructs-that can be used to define completely dynamic higher-order dataflow-networks.  The user sees the functionality through a hybrid-monadic-applicative interface, where stateful signals can only be-created through a specialised monad, while most combinators are purely-applicative.  The combinators build up a network of interconnected-mutable references in the background.  The network is executed-iteratively, where each superstep consists of three phases: sampling,-aging, and finalisation.  As an example, the following code is a-possible way to define an approximation of our beloved trig functions:--@- (sine,cosine) <- mdo-   s <- integral 0 c-   c <- integral 1 (-s)-   return (s,c)-@--Note that @integral@ is not a primitive, it can be defined by the user-as a transfer function.  A possible implementation that can be used on-any 'Fractional' signal looks like this:--@- integral x0 s = transfer x0 (\\dt x x0 -> x0+x*realToFrac dt) s-@--Head to "FRP.Elerea.Legacy.Internal" for the implementation details.-To get a general idea how to use the library, check out the sources in-the @elerea-examples@ package.--The "FRP.Elerea" branch provides a similar interface with a rather-different underlying structure, which is likely to be more efficient.---}--module FRP.Elerea.Legacy-    ( DTime, Sink, Signal, SignalMonad-    , createSignal, superstep-    , external-    , stateful, transfer, delay-    , sampler, generator-    , storeJust, toMaybe-    , edge-    , keepAlive, (.@.)-    , (==@), (/=@), (<@), (<=@), (>=@), (>@)-    , (&&@), (||@)-    , signalDebug-) where--import Control.Applicative-import FRP.Elerea.Legacy.Internal--infix  4 ==@, /=@, <@, <=@, >=@, >@-infixr 3 &&@-infixr 2 ||@--{-| A short alternative name for 'keepAlive'. -}--(.@.) :: Signal a -> Signal t -> Signal a-(.@.) = keepAlive--{-| The `edge` transfer function takes a bool signal and emits another-bool signal that turns true only at the moment when there is a rising-edge on the input. -}--edge :: Signal Bool -> SignalMonad (Signal Bool)-edge b = delay True b >>= \db -> return $ (not <$> db) &&@ b--{-| The `storeJust` transfer function behaves as a latch on a 'Maybe'-input: it keeps its state when the input is 'Nothing', and replaces it-with the input otherwise. -}--storeJust :: a                      -- ^ Initial output-          -> Signal (Maybe a)       -- ^ Maybe signal to latch on-          -> SignalMonad (Signal a)-storeJust x0 s = transfer x0 store s-    where store _ Nothing  x = x-          store _ (Just x) _ = x--{-| Point-wise equality of two signals. -}--(==@) :: Eq a => Signal a -> Signal a -> Signal Bool-(==@) = liftA2 (==)--{-| Point-wise inequality of two signals. -}--(/=@) :: Eq a => Signal a -> Signal a -> Signal Bool-(/=@) = liftA2 (/=)--{-| Point-wise comparison of two signals. -}--(<@) :: Ord a => Signal a -> Signal a -> Signal Bool-(<@) = liftA2 (<)--{-| Point-wise comparison of two signals. -}--(<=@) :: Ord a => Signal a -> Signal a -> Signal Bool-(<=@) = liftA2 (<=)--{-| Point-wise comparison of two signals. -}--(>=@) :: Ord a => Signal a -> Signal a -> Signal Bool-(>=@) = liftA2 (>=)--{-| Point-wise comparison of two signals. -}--(>@) :: Ord a => Signal a -> Signal a -> Signal Bool-(>@) = liftA2 (>)--{-| Point-wise OR of two boolean signals. -}--(||@) :: Signal Bool -> Signal Bool -> Signal Bool-(||@) = liftA2 (||)--{-| Point-wise AND of two boolean signals. -}--(&&@) :: Signal Bool -> Signal Bool -> Signal Bool-(&&@) = liftA2 (&&)
− FRP/Elerea/Legacy/Delayed.hs
@@ -1,374 +0,0 @@-{-|--Note: this module is deprecated, because automatic delays are-ill-defined, and not very useful in practice anyway.  Experience with-the library suggests that instantaneous loops are relatively easy to-avoid.--This version differs from the parametric one in introducing automatic-delays.  In practice, if a dependency loop involves a 'transfer'-primitive, it will be resolved during runtime even if transfer-functions are not delayed by default.  Also, the until construct and-multi-signal transfer variants are missing from this module.--The interface of this module differs from the old Elerea in the-following ways:--* the delta time argument is generalised to an arbitrary type, so it-  is possible to do without 'external' altogether in case someone-  wants to do so;--* there is no 'sampler' any more, it is substituted by 'join', as-  signals are monads;--* 'generator' has been conceptually simplified, so it's a more basic-  primitive now;--* all signals are aged regardless of whether they are sampled-  (i.e. their behaviour doesn't depend on the context any more);--* the user needs to cache the results of applicative operations to be-  reused in multiple places explicitly using the 'memo' combinator.---}--module FRP.Elerea.Legacy.Delayed-    ( Signal-    , SignalGen-    , start-    , external-    , externalMulti-    , delay-    , stateful-    , transfer-    , memo-    , generator-    , noise-    , getRandom-    , debug-    ) where--import Control.Applicative-import Control.Concurrent.MVar-import Control.Monad-import Control.Monad.Fix-import Data.IORef-import Data.Maybe-import System.Mem.Weak-import System.Random.Mersenne---- | A signal can be thought of as a function of type @Nat -> a@, and--- its 'Monad' instance agrees with that intuition.  Internally, is--- represented by a sampling computation.-newtype Signal p a = S { unS :: p -> IO a }---- | A dynamic set of actions to update a network without breaking--- consistency.-type UpdatePool p = [Weak (p -> IO (), IO ())]---- | A signal generator is the only source of stateful signals.--- Internally, computes a signal structure and adds the new variables--- to an existing update pool.-newtype SignalGen p a = SG { unSG :: IORef (UpdatePool p) -> IO a }---- | The phases every signal goes through during a superstep: before--- or after sampling.-data Phase s a = Ready s | Sampling s | Aged s a--instance Functor (Signal p) where-  fmap = liftM--instance Applicative (Signal p) where-  pure = return-  (<*>) = ap--instance Monad (Signal p) where-  return = S . const . return-  S g >>= f = S $ \p -> g p >>= \x -> unS (f x) p--instance Functor (SignalGen p) where-  fmap = liftM--instance Applicative (SignalGen p) where-  pure = return-  (<*>) = ap--instance Monad (SignalGen p) where-  return = SG . const . return-  SG g >>= f = SG $ \p -> g p >>= \x -> unSG (f x) p--instance MonadFix (SignalGen p) where-  mfix f = SG $ \p -> mfix (($p).unSG.f)---- | Embedding a signal into an 'IO' environment.  Repeated calls to--- the computation returned cause the whole network to be updated, and--- the current sample of the top-level signal is produced as a result.--- The computation accepts a global parameter that will be distributed--- to all signals.  For instance, this can be the time step, if we--- want to model continuous-time signals.-start :: SignalGen p (Signal p a) -- ^ the generator of the top-level signal-      -> IO (p -> IO a)           -- ^ the computation to sample the signal-start (SG gen) = do-  pool <- newIORef []-  (S sample) <- gen pool--  ptrs0 <- readIORef pool-  writeIORef pool []-  (as0,cs0) <- unzip . map fromJust <$> mapM deRefWeak ptrs0-  let ageStatic param = mapM_ ($param) as0-      commitStatic = sequence_ cs0--  return $ \param -> do-    let update [] ptrs age commit = do-          writeIORef pool ptrs-          ageStatic param >> age-          commitStatic >> commit-        update (p:ps) ptrs age commit = do-          r <- deRefWeak p-          case r of-            Nothing -> update ps ptrs age commit-            Just (a,c) -> update ps (p:ptrs) (age >> a param) (commit >> c)--    res <- sample param-    ptrs <- readIORef pool-    update ptrs [] (return ()) (return ())-    return res---- | Auxiliary function used by all the primitives that create a--- mutable variable.-addSignal :: (p -> Phase s a -> IO a)  -- ^ sampling function-          -> (p -> Phase s a -> IO ()) -- ^ aging function-          -> IORef (Phase s a)         -- ^ the mutable variable behind the signal-          -> IORef (UpdatePool p)      -- ^ the pool of update actions-          -> IO (Signal p a)-addSignal sample age ref pool = do-  let  commit (Aged s _) = Ready s-       commit _          = error "commit error: signal not aged"--       sig = S $ \p -> readIORef ref >>= sample p--  update <- mkWeak sig (\p -> readIORef ref >>= age p, modifyIORef ref commit) Nothing-  modifyIORef pool (update:)-  return sig---- | The 'delay' transfer function emits the value of a signal from--- the previous superstep, starting with the filler value given in the--- first argument.-delay :: a                        -- ^ initial output-      -> Signal p a               -- ^ the signal to delay-      -> SignalGen p (Signal p a)-delay x0 (S s) = SG $ \pool -> do-  ref <- newIORef (Ready x0)--  let  sample _ (Ready x)  = return x-       sample _ (Aged _ x) = return x-       sample _ _          = error "sampling eror: delay"--       age p (Ready x) = s p >>= \x' -> x' `seq` writeIORef ref (Aged x' x)-       age _ _         = return ()--  addSignal sample age ref pool---- | Memoising combinator.  It can be used to cache results of--- applicative combinators in case they are used in several places.--- Other than that, it is equivalent to 'return'.-memo :: Signal p a               -- ^ signal to memoise-     -> SignalGen p (Signal p a)-memo (S s) = SG $ \pool -> do-  ref <- newIORef (Ready undefined)--  let  sample p (Ready _)  = s p >>= \x -> writeIORef ref (Aged undefined x) >> return x-       sample _ (Aged _ x) = return x-       sample _ _          = error "sampling eror: memo"--       age p (Ready _) = s p >>= \x -> writeIORef ref (Aged undefined x)-       age _ _         = return ()--  addSignal sample age ref pool---- | A reactive signal that takes the value to output from a monad--- carried by its input.  It is possible to create new signals in the--- monad.-generator :: Signal p (SignalGen p a) -- ^ a stream of generators to potentially run-          -> SignalGen p (Signal p a)-generator (S gen) = SG $ \pool -> do-  ref <- newIORef (Ready undefined)--  let  next p = ($pool).unSG =<< gen p--       sample p (Ready _)  = next p >>= \x' -> writeIORef ref (Aged x' x') >> return x'-       sample _ (Aged _ x) = return x-       sample _ _          = error "sampling eror: generator"--       age p (Ready _) = next p >>= \x' -> writeIORef ref (Aged x' x')-       age _ _         = return ()--  addSignal sample age ref pool---- | A signal that can be directly fed through the sink function--- returned.  This can be used to attach the network to the outer--- world.  Note that this is optional, as all the input of the network--- can be fed in through the global parameter, although that is not--- really convenient for many signals.-external :: a                           -- ^ initial value-         -> IO (Signal p a, a -> IO ()) -- ^ the signal and an IO function to feed it-external x = do-  ref <- newIORef x-  return (S (const (readIORef ref)), writeIORef ref)---- | An event-like signal that can be fed through the sink function--- returned.  The signal carries a list of values fed in since the--- last sampling, i.e. it is constantly [] if the sink is never--- invoked.  The order of elements is reversed, so the last value--- passed to the sink is the head of the list.  Note that unlike--- 'external' this function only returns a generator to be used within--- the expression constructing the top-level stream, and this--- generator can only be used once.-externalMulti :: IO (SignalGen p (Signal p [a]), a -> IO ()) -- ^ a generator for the event signal and the associated sink-externalMulti = do-  var <- newMVar []-  return (SG $ \pool -> do-             let sig = S $ const (readMVar var)-             update <- mkWeak sig (const (return ()),takeMVar var >> putMVar var []) Nothing-             modifyIORef pool (update:)-             return sig-         ,\val -> do  vals <- takeMVar var-                      putMVar var (val:vals)-         )---- | A pure stateful signal.  The initial state is the first output,--- and every following output is calculated from the previous one and--- the value of the global parameter.-stateful :: a -> (p -> a -> a) -> SignalGen p (Signal p a)-stateful x0 f = SG $ \pool -> do-  ref <- newIORef (Ready x0)--  let  sample _ (Ready x)  = return x-       sample _ (Aged _ x) = return x-       sample _ _          = error "sampling eror: stateful"--       age p (Ready x) = let x' = f p x in x' `seq` writeIORef ref (Aged x' x)-       age _ _         = return ()--  addSignal sample age ref pool---- | A stateful transfer function.  The current input affects the--- current output, i.e. the initial state given in the first argument--- is considered to appear before the first output, and can never be--- observed.  Every output is derived from the current value of the--- input signal, the global parameter and the previous output.  The--- only exception is when a transfer function sits in a loop without a--- delay.  In this case, a delay will be inserted at a single place--- during runtime (i.e. the previous output of the node affected will--- be reused) to resolve the circular dependency.-transfer :: a -> (p -> t -> a -> a) -> Signal p t -> SignalGen p (Signal p a)-transfer x0 f (S s) = SG $ \pool -> do-  ref <- newIORef (Ready x0)--  let  sample p (Ready x)  = do-         writeIORef ref (Sampling x)-         y <- s p-         let x' = f p y x-         x' `seq` writeIORef ref (Aged x' x')-         return x'-       sample _ (Sampling x) = return x -- Reusing previous output: automatic delay-       sample _ (Aged _ x) = return x--       age p (Ready x) = do-         y <- s p-         let x' = f p y x-         x' `seq` writeIORef ref (Aged x' x')-       age _ _         = return () -- If it is Sampling, we'll error out later--  addSignal sample age ref pool---- | A random signal.-noise :: MTRandom a => SignalGen p (Signal p a)-noise = memo (S (const randomIO))---- | A random source within the 'SignalGen' monad.-getRandom :: MTRandom a => SignalGen p a-getRandom = SG (const randomIO)---- | A printing action within the 'SignalGen' monad.-debug :: String -> SignalGen p ()-debug = SG . const . putStrLn---- | The @Show@ instance is only defined for the sake of 'Num'...-instance Show (Signal p a) where-  showsPrec _ _ s = "<SIGNAL>" ++ s---- | Equality test is impossible.-instance Eq (Signal p a) where-  _ == _ = False---- | Error message for unimplemented instance functions.-unimp :: String -> a-unimp = error . ("Signal: "++)--instance Ord t => Ord (Signal p t) where-  compare = unimp "compare"-  min = liftA2 min-  max = liftA2 max--instance Enum t => Enum (Signal p t) where-  succ = fmap succ-  pred = fmap pred-  toEnum = pure . toEnum-  fromEnum = unimp "fromEnum"-  enumFrom = unimp "enumFrom"-  enumFromThen = unimp "enumFromThen"-  enumFromTo = unimp "enumFromTo"-  enumFromThenTo = unimp "enumFromThenTo"--instance Bounded t => Bounded (Signal p t) where-  minBound = pure minBound-  maxBound = pure maxBound--instance Num t => Num (Signal p t) where-  (+) = liftA2 (+)-  (-) = liftA2 (-)-  (*) = liftA2 (*)-  signum = fmap signum-  abs = fmap abs-  negate = fmap negate-  fromInteger = pure . fromInteger--instance Real t => Real (Signal p t) where-  toRational = unimp "toRational"--instance Integral t => Integral (Signal p t) where-  quot = liftA2 quot-  rem = liftA2 rem-  div = liftA2 div-  mod = liftA2 mod-  quotRem a b = (fst <$> qrab,snd <$> qrab)-    where qrab = quotRem <$> a <*> b-  divMod a b = (fst <$> dmab,snd <$> dmab)-    where dmab = divMod <$> a <*> b-  toInteger = unimp "toInteger"--instance Fractional t => Fractional (Signal p t) where-  (/) = liftA2 (/)-  recip = fmap recip-  fromRational = pure . fromRational--instance Floating t => Floating (Signal p t) where-  pi = pure pi-  exp = fmap exp-  sqrt = fmap sqrt-  log = fmap log-  (**) = liftA2 (**)-  logBase = liftA2 logBase-  sin = fmap sin-  tan = fmap tan-  cos = fmap cos-  asin = fmap asin-  atan = fmap atan-  acos = fmap acos-  sinh = fmap sinh-  tanh = fmap tanh-  cosh = fmap cosh-  asinh = fmap asinh-  atanh = fmap atanh-  acosh = fmap acosh
− FRP/Elerea/Legacy/Graph.hs
@@ -1,169 +0,0 @@-{-# LANGUAGE ExistentialQuantification #-}-{-# OPTIONS_GHC -fno-warn-name-shadowing #-}--{-|--This module provides some means to visualise the signal structure.---}--module FRP.Elerea.Legacy.Graph (signalToDot) where--import Data.IORef-import qualified Data.Map as Map-import Foreign.Ptr-import Foreign.StablePtr-import FRP.Elerea.Legacy.Internal--type Id = Int--type SignalStore = Map.Map Id SignalInfo--data SignalInfo-    = Const-    | Stateful-    | Transfer Id-    | App Id Id-    | Sampler Id-    | Generator Id Id-    | External-    | Delay Id-    | Lift1 Id-    | Lift2 Id Id-    | Lift3 Id Id Id-    | Lift4 Id Id Id Id-    | Lift5 Id Id Id Id Id-    | None--getPtr :: a -> IO Id-getPtr x = fmap (fromIntegral . ptrToIntPtr . castStablePtrToPtr) (newStablePtr x)--buildStore :: SignalStore -> Signal a -> IO (Id,SignalStore)-buildStore st (S r) = do-  p <- getPtr r-  case Map.lookup p st of-    Just _  -> return (p,st)-    Nothing -> do Ready s <- readIORef r-                  st' <- insertSignal st p s-                  return (p,st')--insertSignal :: SignalStore -> Id -> SignalNode a -> IO SignalStore-insertSignal st p (SNK _) = return (Map.insert p Const st)-insertSignal st p (SNS _ _) = return (Map.insert p Stateful st)-insertSignal st p (SNT s _ _) = do-  (s',st') <- buildStore (Map.insert p None st) s-  return (Map.insert p (Transfer s') st')-insertSignal st p (SNA sf sx) = do-  (sf',st') <- buildStore (Map.insert p None st) sf-  (sx',st'') <- buildStore st' sx-  return (Map.insert p (App sf' sx') st'')-insertSignal st p (SNH ss _) = do-  (ss',st') <- buildStore (Map.insert p None st) ss-  return (Map.insert p (Sampler ss') st')-insertSignal st p (SNM b sm) = do-  (b',st') <- buildStore (Map.insert p None st) b-  (sm',st'') <- buildStore st' sm-  return (Map.insert p (Generator b' sm') st'')-insertSignal st p (SNE _) = return (Map.insert p External st)-insertSignal st p (SND _ s) = do-  (s',st') <- buildStore (Map.insert p None st) s-  return (Map.insert p (Delay s') st')-insertSignal st p (SNKA (S r) _) = do-  Ready s <- readIORef r-  insertSignal st p s-insertSignal st p (SNF1 _ s1) = do-  (s1',st') <- buildStore (Map.insert p None st) s1-  return (Map.insert p (Lift1 s1') st')-insertSignal st p (SNF2 _ s1 s2) = do-  (s1',st') <- buildStore (Map.insert p None st) s1-  (s2',st'') <- buildStore st' s2-  return (Map.insert p (Lift2 s1' s2') st'')-insertSignal st p (SNF3 _ s1 s2 s3) = do-  (s1',st') <- buildStore (Map.insert p None st) s1-  (s2',st'') <- buildStore st' s2-  (s3',st''') <- buildStore st'' s3-  return (Map.insert p (Lift3 s1' s2' s3') st''')-insertSignal st p (SNF4 _ s1 s2 s3 s4) = do-  (s1',st') <- buildStore (Map.insert p None st) s1-  (s2',st'') <- buildStore st' s2-  (s3',st''') <- buildStore st'' s3-  (s4',st'''') <- buildStore st''' s4-  return (Map.insert p (Lift4 s1' s2' s3' s4') st'''')-insertSignal st p (SNF5 _ s1 s2 s3 s4 s5) = do-  (s1',st') <- buildStore (Map.insert p None st) s1-  (s2',st'') <- buildStore st' s2-  (s3',st''') <- buildStore st'' s3-  (s4',st'''') <- buildStore st''' s4-  (s5',st''''') <- buildStore st'''' s5-  return (Map.insert p (Lift5 s1' s2' s3' s4' s5') st''''')--nodeLabel :: Maybe Id -> SignalInfo -> [Char]-nodeLabel id node = case node of-                      Const           -> "const"-                      Stateful        -> "stateful"-                      Transfer _      -> "transfer"-                      App _ _         -> "app"-                      Sampler _       -> "sampler"-                      Generator _ _   -> "generator"-                      External        -> "external"-                      Delay _         -> "delay"-                      Lift1 _         -> "fun1"-                      Lift2 _ _       -> "fun2"-                      Lift3 _ _ _     -> "fun3"-                      Lift4 _ _ _ _   -> "fun4"-                      Lift5 _ _ _ _ _ -> "fun5"-                      None            -> "NONE"-                    ++ (maybe "" show id)--{-|--Traversing the network starting from the given signal and converting-it into a string containing the graph in Graphviz-(<http://www.graphviz.org/>) dot format.  Stateful nodes are coloured-according to their type.--The results might differ depending on whether this function is called-before or after sampling (this also affects the actual network!), but-the networks should be still equivalent.---}--signalToDot :: Signal a -> IO String-signalToDot s = do-  (_,st) <- buildStore Map.empty s-  let rules = map mkRule (Map.assocs st)-      mkRule (id,n) = "  " ++ name ++ attrs ++ edges-          where name = nodeLabel (Just id) n-                attrs = mkLabel (nodeLabel Nothing n) ("style=filled,fillcolor=\"#" ++ nodeCol ++ "\",shape=" ++ nodeShape)-                edges = case n of-                  Transfer s           -> mkEdge s "\"\""-                  App sf sx            -> mkEdge sf "f" ++ mkEdge sx "x"-                  Sampler ss           -> mkEdge ss "\"\""-                  Generator b sm       -> mkEdge b "ctl" ++ mkEdge sm "gen"-                  Delay s              -> mkEdge s "\"\""-                  Lift1 s1             -> mkEdge s1 "x1"-                  Lift2 s1 s2          -> mkEdge s1 "x1" ++ mkEdge s2 "x2"-                  Lift3 s1 s2 s3       -> mkEdge s1 "x1" ++ mkEdge s2 "x2" ++ mkEdge s3 "x3"-                  Lift4 s1 s2 s3 s4    -> mkEdge s1 "x1" ++ mkEdge s2 "x2" ++ mkEdge s3 "x3" ++ mkEdge s4 "x4"-                  Lift5 s1 s2 s3 s4 s5 -> mkEdge s1 "x1" ++ mkEdge s2 "x2" ++ mkEdge s3 "x3" ++ mkEdge s4 "x4" ++ mkEdge s5 "x5"-                  _                    -> ""-                mkEdge endId label = "  " ++ name ++ " -> " ++-                                     nodeLabel (Just endId) (st Map.! endId) ++-                                     mkLabel label "dir=back"-                mkLabel name attrs = " [label=" ++ name ++ "," ++ attrs ++ "];\n"-                nodeCol = case n of-                  Transfer _    -> "ffcc99"-                  Sampler _     -> "99ccff"-                  Generator _ _ -> "ccffff"-                  External      -> "ccff99"-                  Stateful      -> "ffffcc"-                  Delay _       -> "ffccff"-                  _             -> "ffffff"-                nodeShape = case n of-                  Transfer _    -> "diamond"-                  Sampler _     -> "hexagon"-                  Generator _ _ -> "house"-                  External      -> "invtriangle"-                  Delay _       -> "box"-                  _             -> "ellipse"-  return $ "digraph G {\n" ++ concat rules ++ "}\n"
− FRP/Elerea/Legacy/Internal.hs
@@ -1,546 +0,0 @@-{-# LANGUAGE ExistentialQuantification, GeneralizedNewtypeDeriving #-}-{-# OPTIONS_GHC -fno-warn-name-shadowing #-}--{-|--This is the core module of Elerea, which contains the signal-implementation and the atomic constructors.--The basic idea is to create a dataflow network whose structure closely-resembles the user's definitions by turning each combinator into a-mutable variable (an 'IORef').  In other words, each signal is-represented by a variable.  Such a variable contains information about-the operation to perform and (depending on the operation) references-to other signals.  For instance, a pointwise function application-created by the '<*>' operator contains an 'SNA' node, which holds two-references: one to the function signal and another to the argument-signal.--In order to have a pure(-looking) applicative interface for the most-part, the library relies on 'unsafePerformIO' to create the references-of stateless signals, while stateful signals have to be obtained from-a special 'SignalMonad', which is just a wrapping of 'IO' that doesn't-allow any other action to be performed.--The execution of the network is explicitly marked as an IO operation.-The core library exposes a single function to animate the network-called 'superstep', which takes a signal and a time interval, and-mutates all the variables the signal depends on.  It is supposed to be-called repeatedly in a loop that also takes care of user input.--To ensure consistency, a superstep has three phases: sampling, aging-and finalisation.  Each signal reachable from the top-level signal-passed to 'superstep' is sampled at the current point of time-('sample'), and the sample is stored along with the old signal in its-reference.  If the value of a signal is requested multiple times, the-sample is simply reused.  After successfully sampling the top-level-signal, the network is traversed again to advance by the desired time-('advance'), and when that's completed, the finalisation process-throws away the intermediate samples and marks the aged signals as the-current ones, ready to be sampled again.  If there is a dependency-loop, the system tries to use the 'sampleDelayed' function instead of-'sample' to get a useful value at the problematic spot instead of-entering an infinite loop.  Evaluation is initiated by the-'signalValue' function (which is used in both the sampling and the-aging phase to calculate samples and retrieve the cached values if-they are requested again), aging is performed by 'age', while-finalisation is done by 'commit'.  Since these functions are invoked-recursively on a data structure with existential types, their types-also need to be explicity quantified.--As a bonus, applicative nodes are automatically collapsed into lifted-functions of up to five arguments.  This optimisation significantly-reduces the number of nodes in the network.---}--module FRP.Elerea.Legacy.Internal where--import Control.Applicative-import Control.Monad-import Control.Monad.Fix-import Data.IORef-import System.IO.Unsafe---- * Implementation---- ** Some type synonyms--{-| Time is continuous.  Nothing fancy. -}--type DTime = Double--{-| Sinks are used when feeding input into peripheral-bound signals. -}--type Sink a = a -> IO ()---- ** The data structures behind signals--{-| A restricted monad to create stateful signals in. -}--newtype SignalMonad a = SM { createSignal :: IO a } deriving (Monad,Applicative,Functor,MonadFix)--{-| A printing function that can be used in the 'SignalMonad'.-Provided for debugging purposes. -}--signalDebug :: Show a => a -> SignalMonad ()-signalDebug = SM . print--{-| A signal is conceptually a time-varying value. -}--newtype Signal a = S (IORef (SignalTrans a))--{-| A node can have four states that distinguish various stages of-sampling and aging. -}--data SignalTrans a-    -- | @Ready s@ is simply the signal @s@ that was not sampled yet-    = Ready (SignalNode a)-    -- | @Sampling s@ is signal @s@ after its current value was-    -- requested, but not yet delivered-    | Sampling (SignalNode a)-    -- | @Sampled x s@ is signal @s@ paired with its current value @x@-    | Sampled a (SignalNode a)-    -- | @Aged x s@ is the aged version of signal @s@ paired with its-    -- current value @x@-    | Aged a (SignalNode a)--{-| The possible structures of a node are defined by the 'SignalNode'-type.  Note that the @SNFx@ nodes are only needed to optimise-applicatives, they can all be expressed in terms of @SNK@ and-@SNA@. -}--data SignalNode a-    -- | @SNK x@: constantly @x@-    = SNK a-    -- | @SNS x t@: stateful generator, where @x@ is current state and-    -- @t@ is the update function-    | SNS a (DTime -> a -> a)-    -- | @SNT s x t@: stateful transfer function, which also depends-    -- on an input signal @s@-    | forall t . SNT (Signal t) a (DTime -> t -> a -> a)-    -- | @SNA sf sx@: pointwise function application-    | forall t . SNA (Signal (t -> a)) (Signal t)-    -- | @SNH ss r@: the higher-order signal @ss@ collapsed into a-    -- signal cached in reference @r@; @r@ is used during the aging-    -- phase-    | SNH (Signal (Signal a)) (IORef (Signal a))-    -- | @SNM b sm@: signal generator that executes the monad carried-    -- by @sm@ whenever @b@ is true, and outputs the result (or-    -- undefined when @b@ is false)-    | SNM (Signal Bool) (Signal (SignalMonad a))-    -- | @SNE r@: opaque reference to connect peripherals-    | SNE (IORef a)-    -- | @SND s@: the @s@ signal delayed by one superstep-    | SND a (Signal a)-    -- | @SNKA s l@: equivalent to @s@ while aging signal @l@-    | forall t . SNKA (Signal a) (Signal t)-    -- | @SNF1 f@: @fmap f@-    | forall t . SNF1 (t -> a) (Signal t)-    -- | @SNF2 f@: @liftA2 f@-    | forall t1 t2 . SNF2 (t1 -> t2 -> a) (Signal t1) (Signal t2)-    -- | @SNF3 f@: @liftA3 f@-    | forall t1 t2 t3 . SNF3 (t1 -> t2 -> t3 -> a) (Signal t1) (Signal t2) (Signal t3)-    -- | @SNF4 f@: @liftA4 f@-    | forall t1 t2 t3 t4 . SNF4 (t1 -> t2 -> t3 -> t4 -> a) (Signal t1) (Signal t2) (Signal t3) (Signal t4)-    -- | @SNF5 f@: @liftA5 f@-    | forall t1 t2 t3 t4 t5 . SNF5 (t1 -> t2 -> t3 -> t4 -> t5 -> a) (Signal t1) (Signal t2) (Signal t3) (Signal t4) (Signal t5)--{-| You can uncomment the verbose version of this function to see the-applicative optimisations in action. -}--debugLog :: String -> IO a -> IO a---debugLog s io = putStrLn s >> io-debugLog _ io = io--instance Functor Signal where-    fmap = (<*>) . pure--{-| The 'Applicative' instance with run-time optimisation.  The '<*>'-operator tries to move all the pure parts to its left side in order to-flatten the structure, hence cutting down on book-keeping costs.  Since-applicatives are used with pure functions and lifted values most of-the time, one can gain a lot by merging these nodes. -}--instance Applicative Signal where-    -- | A constant signal-    pure = makeSignalUnsafe . SNK-    -- | Point-wise application of a function and a data signal (like @ZipList@)----  --mf <*> mx = sampler (fmap (\f -> sampler (fmap (pure . f) mx)) mf)---  sf <*> sx = sampler (makeSignalUnsafe (SNF1 (\f -> sampler (makeSignalUnsafe (SNF1 (pure . f) sx))) sf))--    f@(S rf) <*> x@(S rx) = unsafePerformIO $ do-      -- General fall-back case-      c <- newIORef (Ready (SNA f x))--      let opt s = writeIORef c (Ready s)--      -- Optimisations might go haywire in the presence of loops,-      -- so we need to prepare to meeting undefined references by-      -- wrapping reads into exception handlers.--      flip catch (const (debugLog "no_fun" $ return ())) $ do-        Ready nf <- readIORef rf--        merged <- flip catch (const (debugLog "no_arg" $ return False)) $ do-          -- Merging constant branches from the two sides-          Ready nx <- readIORef rx-          case (nf,nx) of-            (SNK g,SNK y)                  -> debugLog "merge_00" $ opt (SNK (g y))-            (SNK g,SNF1 h y1)              -> debugLog "merge_01" $ opt (SNF1 (g.h) y1)-            (SNK g,SNF2 h y1 y2)           -> debugLog "merge_02" $ opt (SNF2 (\y1 y2 -> g (h y1 y2)) y1 y2)-            (SNK g,SNF3 h y1 y2 y3)        -> debugLog "merge_03" $ opt (SNF3 (\y1 y2 y3 -> g (h y1 y2 y3)) y1 y2 y3)-            (SNK g,SNF4 h y1 y2 y3 y4)     -> debugLog "merge_04" $ opt (SNF4 (\y1 y2 y3 y4 -> g (h y1 y2 y3 y4)) y1 y2 y3 y4)-            (SNK g,SNF5 h y1 y2 y3 y4 y5)  -> debugLog "merge_05" $ opt (SNF5 (\y1 y2 y3 y4 y5 -> g (h y1 y2 y3 y4 y5)) y1 y2 y3 y4 y5)-            (SNK g,_)                      -> debugLog "lift_1x" $ opt (SNF1 g x)-            (SNF1 g x1,SNK y)              -> debugLog "merge_10" $ opt (SNF1 (\x1 -> g x1 y) x1)-            (SNF1 g x1,SNF1 h y1)          -> debugLog "merge_11" $ opt (SNF2 (\x1 y1 -> g x1 (h y1)) x1 y1)-            (SNF1 g x1,SNF2 h y1 y2)       -> debugLog "merge_12" $ opt (SNF3 (\x1 y1 y2 -> g x1 (h y1 y2)) x1 y1 y2)-            (SNF1 g x1,SNF3 h y1 y2 y3)    -> debugLog "merge_13" $ opt (SNF4 (\x1 y1 y2 y3 -> g x1 (h y1 y2 y3)) x1 y1 y2 y3)-            (SNF1 g x1,SNF4 h y1 y2 y3 y4) -> debugLog "merge_14" $ opt (SNF5 (\x1 y1 y2 y3 y4 -> g x1 (h y1 y2 y3 y4)) x1 y1 y2 y3 y4)-            (SNF1 g x1,_)                  -> debugLog "lift_2x" $ opt (SNF2 g x1 x)-            (SNF2 g x1 x2,SNK y)           -> debugLog "merge_20" $ opt (SNF2 (\x1 x2 -> g x1 x2 y) x1 x2)-            (SNF2 g x1 x2,SNF1 h y1)       -> debugLog "merge_21" $ opt (SNF3 (\x1 x2 y1 -> g x1 x2 (h y1)) x1 x2 y1)-            (SNF2 g x1 x2,SNF2 h y1 y2)    -> debugLog "merge_22" $ opt (SNF4 (\x1 x2 y1 y2 -> g x1 x2 (h y1 y2)) x1 x2 y1 y2)-            (SNF2 g x1 x2,SNF3 h y1 y2 y3) -> debugLog "merge_23" $ opt (SNF5 (\x1 x2 y1 y2 y3 -> g x1 x2 (h y1 y2 y3)) x1 x2 y1 y2 y3)-            (SNF2 g x1 x2,_)               -> debugLog "lift_3x" $ opt (SNF3 g x1 x2 x)-            (SNF3 g x1 x2 x3,SNK y)        -> debugLog "merge_30" $ opt (SNF3 (\x1 x2 x3 -> g x1 x2 x3 y) x1 x2 x3)-            (SNF3 g x1 x2 x3,SNF1 h y1)    -> debugLog "merge_31" $ opt (SNF4 (\x1 x2 x3 y1 -> g x1 x2 x3 (h y1)) x1 x2 x3 y1)-            (SNF3 g x1 x2 x3,SNF2 h y1 y2) -> debugLog "merge_32" $ opt (SNF5 (\x1 x2 x3 y1 y2 -> g x1 x2 x3 (h y1 y2)) x1 x2 x3 y1 y2)-            (SNF3 g x1 x2 x3,_)            -> debugLog "lift_4x" $ opt (SNF4 g x1 x2 x3 x)-            (SNF4 g x1 x2 x3 x4,SNK y)     -> debugLog "merge_40" $ opt (SNF4 (\x1 x2 x3 x4 -> g x1 x2 x3 x4 y) x1 x2 x3 x4)-            (SNF4 g x1 x2 x3 x4,SNF1 h y1) -> debugLog "merge_41" $ opt (SNF5 (\x1 x2 x3 x4 y1 -> g x1 x2 x3 x4 (h y1)) x1 x2 x3 x4 y1)-            (SNF4 g x1 x2 x3 x4,_)         -> debugLog "lift_5x" $ opt (SNF5 g x1 x2 x3 x4 x)-            (SNF5 g x1 x2 x3 x4 x5,SNK y)  -> debugLog "merge_50" $ opt (SNF5 (\x1 x2 x3 x4 x5 -> g x1 x2 x3 x4 x5 y) x1 x2 x3 x4 x5)-            _                              -> return ()-          return True--        -- Lifting into higher arity not knowing the argument-        when (not merged) $ case nf of-          SNK g              -> debugLog "lift_1" $ opt (SNF1 g x)-          SNF1 g x1          -> debugLog "lift_2" $ opt (SNF2 g x1 x)-          SNF2 g x1 x2       -> debugLog "lift_3" $ opt (SNF3 g x1 x2 x)-          SNF3 g x1 x2 x3    -> debugLog "lift_4" $ opt (SNF4 g x1 x2 x3 x)-          SNF4 g x1 x2 x3 x4 -> debugLog "lift_5" $ opt (SNF5 g x1 x2 x3 x4 x)-          _                  -> return ()--      -- The final version-      return (S c)--{-| The @Show@ instance is only defined for the sake of 'Num'... -}--instance Show (Signal a) where-    showsPrec _ _ s = "<SIGNAL>" ++ s--{-| The equality test checks whether two signals are physically the same. -}--instance Eq (Signal a) where-    S s1 == S s2 = s1 == s2--{-| Error message for unimplemented instance functions. -}--unimp :: String -> a-unimp = error . ("Signal: "++)--instance Ord t => Ord (Signal t) where-    compare = unimp "compare"-    min = liftA2 min-    max = liftA2 max--instance Enum t => Enum (Signal t) where-    succ = fmap succ-    pred = fmap pred-    toEnum = pure . toEnum-    fromEnum = unimp "fromEnum"-    enumFrom = unimp "enumFrom"-    enumFromThen = unimp "enumFromThen"-    enumFromTo = unimp "enumFromTo"-    enumFromThenTo = unimp "enumFromThenTo"--instance Bounded t => Bounded (Signal t) where-    minBound = pure minBound-    maxBound = pure maxBound--instance Num t => Num (Signal t) where-    (+) = liftA2 (+)-    (-) = liftA2 (-)-    (*) = liftA2 (*)-    signum = fmap signum-    abs = fmap abs-    negate = fmap negate-    fromInteger = pure . fromInteger--instance Real t => Real (Signal t) where-    toRational = unimp "toRational"--instance Integral t => Integral (Signal t) where-    quot = liftA2 quot-    rem = liftA2 rem-    div = liftA2 div-    mod = liftA2 mod-    quotRem a b = (fst <$> qrab,snd <$> qrab)-        where qrab = quotRem <$> a <*> b-    divMod a b = (fst <$> dmab,snd <$> dmab)-        where dmab = divMod <$> a <*> b-    toInteger = unimp "toInteger"--instance Fractional t => Fractional (Signal t) where-    (/) = liftA2 (/)-    recip = fmap recip-    fromRational = pure . fromRational--instance Floating t => Floating (Signal t) where-    pi = pure pi-    exp = fmap exp-    sqrt = fmap sqrt-    log = fmap log-    (**) = liftA2 (**)-    logBase = liftA2 logBase-    sin = fmap sin-    tan = fmap tan-    cos = fmap cos-    asin = fmap asin-    atan = fmap atan-    acos = fmap acos-    sinh = fmap sinh-    tanh = fmap tanh-    cosh = fmap cosh-    asinh = fmap asinh-    atanh = fmap atanh-    acosh = fmap acosh---- ** Internal functions to run the network--{-| Creating a reference within the 'SignalMonad'.  Used for stateful-signals. -}--makeSignal :: SignalNode a -> SignalMonad (Signal a)-makeSignal node = SM $ do-  ref <- newIORef (Ready node)-  return (S ref)--{-| Creating a reference as a pure value.  Used for stateless-signals. -}--makeSignalUnsafe :: SignalNode a -> Signal a-makeSignalUnsafe = S . unsafePerformIO . newIORef . Ready--{-| Sampling the signal and all of its dependencies, at the same time.-We don't need the aged signal in the current superstep, only the-current value, so we sample before propagating the changes, which-might require the fresh sample because of recursive definitions. -}--signalValue :: forall a . Signal a -> DTime -> IO a-signalValue (S r) dt = do-  t <- readIORef r-  case t of-    Ready s     -> do writeIORef r (Sampling s)-                      -- TODO: advance can be evaluated in a separate-                      -- thread, since we don't need its result right-                      -- away, only in the next superstep.-                      v <- sample s dt-                      -- We memorise the sample to handle loops-                      -- nicely.  The undefined future signal cannot-                      -- bite us, because we don't need it during the-                      -- evaluation phase.-                      writeIORef r (Sampled v s)-                      return v-    Sampling s  -> do -- We started sampling this already, so there is-                      -- a dependency cycle we have to resolve by-                      -- adding a delay to stateful signals. Stateless-                      -- signals should not form a loop, which is-                      -- obvious...-                      v <- sampleDelayed s dt-                      writeIORef r (Sampled v s)-                      -- Since we are sampling it already, this node-                      -- will be overwritten by the case above when-                      -- the loop is closed.-                      return v-    Sampled v _ -> return v-    Aged v _    -> return v--{-| Aging the network of signals the given signal depends on. -}--age :: forall a . Signal a -> DTime -> IO ()-age (S r) dt = do-  t <- readIORef r-  case t of-    Sampled v s -> do s' <- advance s v dt-                      writeIORef r (Aged v s')-                      -- TODO: branching can be trivially parallelised-                      case s' of-                        SNT s _ _             -> age s dt-                        SNA sf sx             -> age sf dt >> age sx dt-                        SNH ss r              -> age ss dt >> readIORef r >>= \s -> age s dt-                        SNM b sm              -> age b dt >> age sm dt-                        SND _ s               -> age s dt-                        SNKA s l              -> age s dt >> age l dt-                        SNF1 _ s              -> age s dt-                        SNF2 _ s1 s2          -> age s1 dt >> age s2 dt-                        SNF3 _ s1 s2 s3       -> age s1 dt >> age s2 dt >> age s3 dt-                        SNF4 _ s1 s2 s3 s4    -> age s1 dt >> age s2 dt >> age s3 dt >> age s4 dt-                        SNF5 _ s1 s2 s3 s4 s5 -> age s1 dt >> age s2 dt >> age s3 dt >> age s4 dt >> age s5 dt-                        _                     -> return ()-    Aged _ _    -> return ()-    _           -> error "Inconsistent state: signal not sampled properly!"--{-| Finalising aged signals for the next round. -}--commit :: forall a . Signal a -> IO ()-commit (S r) = do-  t <- readIORef r-  case t of-    Aged _ s -> do writeIORef r (Ready s)-                   -- TODO: branching can be trivially parallelised-                   case s of-                     SNT s _ _             -> commit s-                     SNA sf sx             -> commit sf >> commit sx-                     SNH ss r              -> commit ss >> readIORef r >>= \s -> commit s-                     SNM b sm              -> commit b >> commit sm-                     SND _ s               -> commit s-                     SNKA s l              -> commit s >> commit l-                     SNF1 _ s              -> commit s-                     SNF2 _ s1 s2          -> commit s1 >> commit s2-                     SNF3 _ s1 s2 s3       -> commit s1 >> commit s2 >> commit s3-                     SNF4 _ s1 s2 s3 s4    -> commit s1 >> commit s2 >> commit s3 >> commit s4-                     SNF5 _ s1 s2 s3 s4 s5 -> commit s1 >> commit s2 >> commit s3 >> commit s4 >> commit s5-                     _                     -> return ()-    Ready _     -> return ()-    _           -> error "Inconsistent state: signal not aged properly!"--{-| Aging the signal.  Stateful signals have their state forced to-prevent building up big thunks.  The other nodes are structurally-static. -}--advance :: SignalNode a -> a -> DTime -> IO (SignalNode a)-advance (SNS x f)    _ dt = x `seq` return (SNS (f dt x) f)-advance (SNT s _ f)  v _  = v `seq` return (SNT s v f)-advance (SND _ s)    _ dt = do x <- signalValue s dt-                               return (SND x s)-advance s            _ _  = return s--{-| Sampling the signal at the current moment.  This is where static-nodes propagate changes to those they depend on.  Transfer functions-('SNT') work without delay, i.e. the effects of their input signals-can be observed in the same superstep. -}--sample :: SignalNode a -> DTime -> IO a-sample (SNK x)                 _  = return x-sample (SNS x _)               _  = return x-sample (SNT s x f)             dt = do t <- signalValue s dt-                                       return $! f dt t x-sample (SNA sf sx)             dt = signalValue sf dt <*> signalValue sx dt-sample (SNH ss r)              dt = do s <- signalValue ss dt-                                       writeIORef r s-                                       signalValue s dt-sample (SNM b sm)              dt = do c <- signalValue b dt-                                       SM m <- signalValue sm dt-                                       if c then m else return undefined-sample (SNE r)                 _  = readIORef r-sample (SND v _)               _  = return v-sample (SNKA s l)              dt = do _ <- signalValue l dt-                                       signalValue s dt-sample (SNF1 f s)              dt = f <$> signalValue s dt-sample (SNF2 f s1 s2)          dt = liftM2 f (signalValue s1 dt) (signalValue s2 dt)-sample (SNF3 f s1 s2 s3)       dt = liftM3 f (signalValue s1 dt) (signalValue s2 dt) (signalValue s3 dt)-sample (SNF4 f s1 s2 s3 s4)    dt = liftM4 f (signalValue s1 dt) (signalValue s2 dt) (signalValue s3 dt) (signalValue s4 dt)-sample (SNF5 f s1 s2 s3 s4 s5) dt = liftM5 f (signalValue s1 dt) (signalValue s2 dt) (signalValue s3 dt) (signalValue s4 dt) (signalValue s5 dt)--{-| Sampling the signal with some kind of delay in order to resolve-dependency loops.  Transfer functions simply return their previous-output (delays can be considered a special case, because they always-do that, so 'sampleDelayed' is never called with them), while other-types of signals are always handled by the 'sample' function, so it is-not possible to create a working stateful loop composed of solely-stateless combinators. -}--sampleDelayed :: SignalNode a -> DTime -> IO a-sampleDelayed (SNT _ x _) _  = return x-sampleDelayed sn          dt = sample sn dt---- ** Userland combinators--{-| Advancing the whole network that the given signal depends on by-the amount of time given in the second argument. -}--superstep :: Signal a -- ^ the top-level signal-          -> DTime    -- ^ the amount of time to advance-          -> IO a     -- ^ the current value of the signal-superstep world dt = do-  snapshot <- signalValue world dt-  age world dt-  commit world-  return snapshot--{-| A pure stateful signal.  The initial state is the first output. -}--stateful :: a                 -- ^ initial state-         -> (DTime -> a -> a) -- ^ state transformation-         -> SignalMonad (Signal a)-stateful x0 f = makeSignal (SNS x0 f)--{-| A stateful transfer function.  The current input affects the-current output, i.e. the initial state given in the first argument is-considered to appear before the first output, and can only be directly-observed by the `sampleDelayed` function. -}--transfer :: a                      -- ^ initial internal state-         -> (DTime -> t -> a -> a) -- ^ state updater function-         -> Signal t               -- ^ input signal-         -> SignalMonad (Signal a)-transfer x0 f s = makeSignal (SNT s x0 f)--{-| A continuous sampler that flattens a higher-order signal by-outputting its current snapshots. -}--sampler :: Signal (Signal a) -- ^ signal to flatten-        -> Signal a-sampler ss = makeSignalUnsafe (SNH ss (unsafePerformIO (newIORef undefined)))--{-| A reactive signal that takes the value to output from a monad-carried by its input when a boolean control signal is true, otherwise-it outputs 'Nothing'.  It is possible to create new signals in the-monad and also to print debug messages. -}--generator :: Signal Bool            -- ^ control (trigger) signal-          -> Signal (SignalMonad a) -- ^ a stream of monads to potentially run-          -> Signal (Maybe a)-generator b sm = toMaybe <$> b <*> makeSignalUnsafe (SNM b sm)--{-| A helper function to wrap any value in a 'Maybe' depending on a-boolean condition. -}--toMaybe :: Bool -> a -> Maybe a-toMaybe c v = if c then Just v else Nothing--{-| A signal that can be directly fed through the sink function-returned.  This can be used to attach the network to the outer-world. -}--external :: a                     -- ^ initial value-         -> IO (Signal a, Sink a) -- ^ the signal and an IO function to feed it-external x0 = do-  ref <- newIORef x0-  snr <- newIORef (Ready (SNE ref))-  return (S snr,writeIORef ref)--{-| The `delay` transfer function emits the value of a signal from the-previous superstep, starting with the filler value given in the first-argument.  It has to be a primitive, otherwise it could not be used to-prevent automatic delays. -}--delay :: a        -- ^ initial output-      -> Signal a -- ^ the signal to delay-      -> SignalMonad (Signal a)-delay x0 s = makeSignal (SND x0 s)--{-| Dependency injection to allow aging signals whose output is not-necessarily needed to produce the current sample of the first-argument.  It's equivalent to @(flip . liftA2 . flip) const@, as it-evaluates its second argument first. -}--keepAlive :: Signal a -- ^ the actual output-          -> Signal t -- ^ a signal guaranteed to age when this one is sampled-          -> Signal a-keepAlive s l = makeSignalUnsafe (SNKA s l)
FRP/Elerea/Param.hs view
@@ -20,7 +20,6 @@     , start     , external     , externalMulti-    , debug     -- * Basic building blocks     , delay     , generator@@ -34,9 +33,14 @@     , transfer2     , transfer3     , transfer4-    -- * Random sources-    , noise-    , getRandom+    -- * Signals with side effects+    -- $effectful+    , execute+    , effectful+    , effectful1+    , effectful2+    , effectful3+    , effectful4     ) where  import Control.Applicative@@ -47,7 +51,6 @@ import Data.Maybe import Prelude hiding (until) import System.Mem.Weak-import System.Random.Mersenne  -- | A signal represents a value changing over time.  It can be -- thought of as a function of type @Nat -> a@, where the argument is@@ -97,9 +100,8 @@ -- signals in the resulting structure. newtype SignalGen p a = SG { unSG :: IORef UpdatePool -> Signal p -> IO a } --- | The phases every signal goes through during a superstep: before--- or after sampling.-data Phase s a = Ready s | Aged s a+-- | The phases every signal goes through during a superstep.+data Phase a = Ready a | Updated a a  instance Functor (SignalGen p) where   fmap = liftM@@ -109,11 +111,11 @@   (<*>) = ap  instance Monad (SignalGen p) where-  return = SG . const . const . return+  return x = SG $ \_ _ -> return x   SG g >>= f = SG $ \p i -> g p i >>= \x -> unSG (f x) p i  instance MonadFix (SignalGen p) where-  mfix f = SG $ \p i -> mfix (($i).($p).unSG.f)+  mfix f = SG $ \p i -> mfix $ \x -> unSG (f x) p i  -- | Embedding a signal into an 'IO' environment.  Repeated calls to -- the computation returned cause the whole network to be updated, and@@ -141,45 +143,38 @@   pool <- newIORef []   (inp,sink) <- external undefined   S sample <- gen pool inp--  ptrs0 <- readIORef pool-  writeIORef pool []-  (as0,cs0) <- unzip . map fromJust <$> mapM deRefWeak ptrs0-  let ageStatic = sequence_ as0-      commitStatic = sequence_ cs0-   return $ \param -> do-    let update [] ptrs age commit = do-          writeIORef pool ptrs-          ageStatic >> age-          commitStatic >> commit-        update (p:ps) ptrs age commit = do-          r <- deRefWeak p-          case r of-            Nothing -> update ps ptrs age commit-            Just (a,c) -> update ps (p:ptrs) (age >> a) (commit >> c)-+    let deref ptr = (fmap.fmap) ((,) ptr) (deRefWeak ptr)     sink param     res <- sample-    ptrs <- readIORef pool-    update ptrs [] (return ()) (return ())+    (ptrs,acts) <- unzip.catMaybes <$> (mapM deref =<< readIORef pool)+    writeIORef pool ptrs+    mapM_ fst acts+    mapM_ snd acts     return res  -- | Auxiliary function used by all the primitives that create a -- mutable variable.-addSignal :: (Phase s a -> IO a)  -- ^ sampling function-          -> (Phase s a -> IO ()) -- ^ aging function-          -> IORef (Phase s a)    -- ^ the mutable variable behind the signal-          -> IORef UpdatePool     -- ^ the pool of update actions+addSignal :: (a -> IO a)      -- ^ sampling function+          -> (a -> IO ())     -- ^ aging function+          -> IORef (Phase a)  -- ^ the mutable variable behind the signal+          -> IORef UpdatePool -- ^ the pool of update actions           -> IO (Signal a)-addSignal sample age ref pool = do-  let  commit (Aged s _)  = Ready s-       commit _           = error "commit error: signal not aged"+addSignal sample update ref pool = do+  let upd = readIORef ref >>= \v -> case v of+              Ready x -> update x+              _       -> return () -       sig = S $ readIORef ref >>= sample+      fin = readIORef ref >>= \v -> case v of+              Updated x _ -> writeIORef ref $! Ready x+              _           -> error "Signal not updated!" -  update <- mkWeak sig (readIORef ref >>= age, modifyIORef ref commit) Nothing-  modifyIORef pool (update:)+      sig = S $ readIORef ref >>= \v -> case v of+              Ready x     -> sample x+              Updated _ x -> return x++  updateActions <- mkWeak sig (upd,fin) Nothing+  modifyIORef pool (updateActions:)   return sig  -- | The 'delay' combinator is the elementary building block for@@ -226,40 +221,13 @@ delay x0 (S s) = SG $ \pool _ -> do   ref <- newIORef (Ready x0) -  let  sample (Ready x)   = return x-       sample (Aged _ x)  = return x--       age (Ready x)  = s >>= \x' -> x' `seq` writeIORef ref (Aged x' x)-       age _          = return ()--  addSignal sample age ref pool---- | Memoising combinator.  It can be used to cache results of--- applicative combinators in case they are used in several places.--- It is observationally equivalent to 'return' in the 'SignalGen'--- monad.------ > memo s = <|s s s s ...|>------ For instance, if @s = f \<$\> s'@, then @f@ will be recalculated--- once for each sampling of @s@.  This can be avoided by writing @s--- \<- memo (f \<$\> s')@ instead.  However, 'memo' incurs a small--- overhead, therefore it should not be used blindly.------ All the functions defined in this module return memoised signals.--- Just like 'delay', it is independent of the global input.-memo :: Signal a               -- ^ signal to memoise-     -> SignalGen p (Signal a)-memo (S s) = SG $ \pool _ -> do-  ref <- newIORef (Ready undefined)--  let  sample (Ready _)      = s >>= \x -> writeIORef ref (Aged undefined x) >> return x-       sample (Aged _ x)     = return x+  let update x = s >>= \x' -> x' `seq` writeIORef ref (Updated x' x) -       age (Ready _)      = s >>= \x -> writeIORef ref (Aged undefined x)-       age _              = return ()+  addSignal return update ref pool -  addSignal sample age ref pool+-- | Auxiliary function.+memoise :: IORef (Phase a) -> a -> IO a+memoise ref x = writeIORef ref (Updated undefined x) >> return x  -- | A reactive signal that takes the value to output from a signal -- generator carried by its input with the sampling time provided as@@ -291,20 +259,37 @@ -- -- Refer to the longer example at the bottom of "FRP.Elerea.Simple" to -- see how it can be used.-generator :: Signal (SignalGen p a) -- ^ a stream of generators to potentially run-          -> SignalGen p (Signal a)-generator (S gen) = SG $ \pool inp -> do+generator :: Signal (SignalGen p a) -- ^ the signal of generators to run+          -> SignalGen p (Signal a) -- ^ the signal of generated structures+generator (S s) = SG $ \pool inp -> do   ref <- newIORef (Ready undefined) -  let  next = ($inp).($pool).unSG =<< gen+  let sample = (s >>= \(SG g) -> g pool inp) >>= memoise ref+  +  addSignal (const sample) (const (() <$ sample)) ref pool -       sample (Ready _)  = next >>= \x' -> writeIORef ref (Aged x' x') >> return x'-       sample (Aged _ x) = return x+-- | Memoising combinator.  It can be used to cache results of+-- applicative combinators in case they are used in several places.+-- It is observationally equivalent to 'return' in the 'SignalGen'+-- monad.+--+-- > memo s = <|s s s s ...|>+--+-- For instance, if @s = f \<$\> s'@, then @f@ will be recalculated+-- once for each sampling of @s@.  This can be avoided by writing @s+-- \<- memo (f \<$\> s')@ instead.  However, 'memo' incurs a small+-- overhead, therefore it should not be used blindly.+--+-- All the functions defined in this module return memoised signals.+-- Just like 'delay', it is independent of the global input.+memo :: Signal a               -- ^ the signal to cache+     -> SignalGen p (Signal a) -- ^ a signal observationally equivalent to the argument+memo (S s) = SG $ \pool _ -> do+  ref <- newIORef (Ready undefined) -       age (Ready _) = next >>= \x' -> writeIORef ref (Aged x' x')-       age _         = return ()+  let sample = s >>= memoise ref -  addSignal sample age ref pool+  addSignal (const sample) (const (() <$ sample)) ref pool  -- | A signal that is true exactly once: the first time the input -- signal is true.  Afterwards, it is constantly false, and it holds@@ -350,9 +335,9 @@    rsmp <- mfix $ \rs -> newIORef $ do     x <- s-    writeIORef ref (Aged undefined x)+    writeIORef ref (Updated undefined x)     when x $ writeIORef rs $ do-      writeIORef ref (Aged undefined False)+      writeIORef ref (Updated undefined False)       return False     return x @@ -430,8 +415,8 @@              update <- mkWeak sig (return (),takeMVar var >> putMVar var []) Nothing              modifyIORef pool (update:)              return sig-         ,\val -> do  vals <- takeMVar var-                      putMVar var (val:vals)+         ,\val -> do vals <- takeMVar var+                     putMVar var (val:vals)          )  -- | A direct stateful transformation of the input.  The initial state@@ -522,28 +507,117 @@     sig' <- delay x0 sig     memo (liftM5 f inp s1 s2 s3 s4 `ap` sig') --- | A random signal.+{- $effectful++The following combinators are primarily aimed at library implementors+who wish build abstractions to effectful libraries on top of Elerea.++-}++-- | An IO action executed in the 'SignalGen' monad. Can be used as+-- `liftIO`.+execute :: IO a -> SignalGen p a+execute act = SG $ \_ _ -> act++-- | A signal that executes a given IO action once at every sampling. --+-- In essence, this combinator provides cooperative multitasking+-- capabilities, and its primary purpose is to assist library writers+-- in wrapping effectful APIs as conceptually pure signals.  If there+-- are several effectful signals in the system, their order of+-- execution is undefined and should not be relied on.+-- -- Example: -- -- > do--- >     smp <- start noise :: IO (IO Double)+-- >     act <- start $ do+-- >         ref <- execute $ newIORef 0+-- >         let accum n = do+-- >                 x <- readIORef ref+-- >                 putStrLn $ "Accumulator: " ++ show x+-- >                 writeIORef ref $! x+n+-- >                 return ()+-- >         effectful1 accum =<< input+-- >     forM_ [4,9,2,1,5] act+--+-- Output:+--+-- > Accumulator: 0+-- > Accumulator: 4+-- > Accumulator: 13+-- > Accumulator: 15+-- > Accumulator: 16+--+-- Another example (requires mersenne-random):+--+-- > do+-- >     smp <- start $ effectful randomIO :: IO (IO Double) -- >     res <- replicateM 5 smp -- >     print res -- -- Output: -- -- > [0.12067753390401374,0.8658877349182655,0.7159264443196786,0.1756941896012891,0.9513646060896676]-noise :: MTRandom a => SignalGen p (Signal a)-noise = memo (S randomIO)+effectful :: IO a                 -- ^ the action to be executed repeatedly+          -> SignalGen p (Signal a)+effectful act = SG $ \pool _ -> do+  ref <- newIORef (Ready undefined) --- | A random source within the 'SignalGen' monad.-getRandom :: MTRandom a => SignalGen p a-getRandom = SG (const (const randomIO))+  let sample = act >>= memoise ref --- | A printing action within the 'SignalGen' monad.-debug :: String -> SignalGen p ()-debug = SG . const . const . putStrLn+  addSignal (const sample) (const (() <$ sample)) ref pool++-- | A signal that executes a parametric IO action once at every+-- sampling.  The parameter is supplied by another signal at every+-- sampling step.+effectful1 :: (t -> IO a)          -- ^ the action to be executed repeatedly+           -> Signal t             -- ^ parameter signal+           -> SignalGen p (Signal a)+effectful1 act (S s) = SG $ \pool _ -> do+  ref <- newIORef (Ready undefined)++  let sample = s >>= act >>= memoise ref++  addSignal (const sample) (const (() <$ sample)) ref pool++-- | Like 'effectful1', but with two parameter signals.+effectful2 :: (t1 -> t2 -> IO a)   -- ^ the action to be executed repeatedly+           -> Signal t1            -- ^ parameter signal 1+           -> Signal t2            -- ^ parameter signal 2+           -> SignalGen p (Signal a)+effectful2 act (S s1) (S s2) = SG $ \pool _ -> do+  ref <- newIORef (Ready undefined)++  let sample = join (liftM2 act s1 s2) >>= memoise ref++  addSignal (const sample) (const (() <$ sample)) ref pool++-- | Like 'effectful1', but with three parameter signals.+effectful3 :: (t1 -> t2 -> t3 -> IO a) -- ^ the action to be executed repeatedly+           -> Signal t1                -- ^ parameter signal 1+           -> Signal t2                -- ^ parameter signal 2+           -> Signal t3                -- ^ parameter signal 3+           -> SignalGen p (Signal a)+effectful3 act (S s1) (S s2) (S s3) = SG $ \pool _ -> do+  ref <- newIORef (Ready undefined)++  let sample = join (liftM3 act s1 s2 s3) >>= memoise ref++  addSignal (const sample) (const (() <$ sample)) ref pool++-- | Like 'effectful1', but with four parameter signals.+effectful4 :: (t1 -> t2 -> t3 -> t4 -> IO a) -- ^ the action to be executed repeatedly+           -> Signal t1                      -- ^ parameter signal 1+           -> Signal t2                      -- ^ parameter signal 2+           -> Signal t3                      -- ^ parameter signal 3+           -> Signal t4                      -- ^ parameter signal 4+           -> SignalGen p (Signal a)+effectful4 act (S s1) (S s2) (S s3) (S s4) = SG $ \pool _ -> do+  ref <- newIORef (Ready undefined)++  let sample = join (liftM4 act s1 s2 s3 s4) >>= memoise ref++  addSignal (const sample) (const (() <$ sample)) ref pool  -- | The @Show@ instance is only defined for the sake of 'Num'... instance Show (Signal a) where
FRP/Elerea/Simple.hs view
@@ -16,7 +16,6 @@     , start     , external     , externalMulti-    , debug     -- * Basic building blocks     , delay     , generator@@ -29,14 +28,13 @@     , transfer3     , transfer4     -- * Signals with side effects+    -- $effectful+    , execute     , effectful     , effectful1     , effectful2     , effectful3     , effectful4-    -- * Random sources-    , noise-    , getRandom     -- * A longer example     -- $example     ) where@@ -49,7 +47,6 @@ import Data.Maybe import Prelude hiding (until) import System.Mem.Weak-import System.Random.Mersenne  -- | A signal represents a value changing over time.  It can be -- thought of as a function of type @Nat -> a@, where the argument is@@ -73,7 +70,7 @@  -- | A dynamic set of actions to update a network without breaking -- consistency.-type UpdatePool = [Weak (IO (),IO ())]+type UpdatePool = [Weak (IO (), IO ())]  -- | A signal generator is the only source of stateful signals.  It -- can be thought of as a function of type @Nat -> a@, where the@@ -101,18 +98,18 @@ data Phase a = Ready a | Updated a a  instance Functor SignalGen where-  fmap = (<*>).pure+  fmap = liftM  instance Applicative SignalGen where   pure = return   (<*>) = ap  instance Monad SignalGen where-  return = SG . const . return+  return x = SG $ \_ -> return x   SG g >>= f = SG $ \p -> g p >>= \x -> unSG (f x) p  instance MonadFix SignalGen where-  mfix f = SG $ \p -> mfix (($p).unSG.f)+  mfix f = SG $ \p -> mfix $ \x -> unSG (f x) p  -- | Embedding a signal into an 'IO' environment.  Repeated calls to -- the computation returned cause the whole network to be updated, and@@ -155,17 +152,17 @@           -> IORef UpdatePool -- ^ the pool of update actions           -> IO (Signal a)    -- ^ the signal created addSignal sample update ref pool = do-  let  upd = readIORef ref >>= \v -> case v of-               Ready x  -> update x-               _        -> return ()+  let upd = readIORef ref >>= \v -> case v of+              Ready x -> update x+              _       -> return () -       fin = readIORef ref >>= \v -> case v of-               Updated x _  -> writeIORef ref $! Ready x-               _            -> error "Signal not updated!"+      fin = readIORef ref >>= \v -> case v of+              Updated x _ -> writeIORef ref $! Ready x+              _           -> error "Signal not updated!" -       sig = S $ readIORef ref >>= \v -> case v of-               Ready x      -> sample x-               Updated _ x  -> return x+      sig = S $ readIORef ref >>= \v -> case v of+              Ready x     -> sample x+              Updated _ x -> return x    updateActions <- mkWeak sig (upd,fin) Nothing   modifyIORef pool (updateActions:)@@ -218,6 +215,10 @@    addSignal return update ref pool +-- | Auxiliary function.+memoise :: IORef (Phase a) -> a -> IO a+memoise ref x = writeIORef ref (Updated undefined x) >> return x+ -- | A reactive signal that takes the value to output from a signal -- generator carried by its input with the sampling time provided as -- the start time for the generated structure.  It is possible to@@ -250,17 +251,10 @@ generator (S s) = SG $ \pool -> do   ref <- newIORef (Ready undefined) -  let sample = do  SG g <- s-                   x <- g pool-                   writeIORef ref (Updated undefined x)-                   return x+  let sample = (s >>= \(SG g) -> g pool) >>= memoise ref    addSignal (const sample) (const (() <$ sample)) ref pool --- | Auxiliary function.-memoise :: IORef (Phase a) -> a -> IO a-memoise ref x = writeIORef ref (Updated undefined x) >> return x- -- | Memoising combinator.  It can be used to cache results of -- applicative combinators in case they are used in several places. -- It is observationally equivalent to 'return' in the 'SignalGen'@@ -402,8 +396,8 @@              update <- mkWeak sig (return (),takeMVar var >> putMVar var []) Nothing              modifyIORef pool (update:)              return sig-         ,\val -> do  vals <- takeMVar var-                      putMVar var (val:vals)+         ,\val -> do vals <- takeMVar var+                     putMVar var (val:vals)          )  -- | A pure stateful signal.  The initial state is the first output,@@ -485,8 +479,19 @@     sig' <- delay x0 sig     memo (liftM5 f s1 s2 s3 s4 sig') +{- $effectful++The following combinators are primarily aimed at library implementors+who wish build abstractions to effectful libraries on top of Elerea.++-}++-- | An IO action executed in the 'SignalGen' monad. Can be used as+-- `liftIO`.+execute :: IO a -> SignalGen a+execute act = SG $ \_ -> act+ -- | A signal that executes a given IO action once at every sampling.--- The IO action is constructed by an initialiser. -- -- In essence, this combinator provides cooperative multitasking -- capabilities, and its primary purpose is to assist library writers@@ -497,14 +502,14 @@ -- Example: -- -- > do--- >     act <- start $ effectful $ do--- >       ref <- newIORef 0--- >       return $ do--- >         x <- readIORef ref--- >         putStrLn $ "Count: " ++ show x--- >         writeIORef ref $! x+1--- >         return ()--- >     replicateM_ 5 act+-- >     smp <- start $ do+-- >         ref <- execute $ newIORef 0+-- >         effectful $ do+-- >             x <- readIORef ref+-- >             putStrLn $ "Count: " ++ show x+-- >             writeIORef ref $! x+1+-- >             return ()+-- >     replicateM_ 5 smp -- -- Output: --@@ -513,94 +518,77 @@ -- > Count: 2 -- > Count: 3 -- > Count: 4-effectful :: IO (IO a)             -- ^ initialiser that yields the action to be executed repeatedly+--+-- Another example (requires mersenne-random):+--+-- > do+-- >     smp <- start $ effectful randomIO :: IO (IO Double)+-- >     res <- replicateM 5 smp+-- >     print res+--+-- Output:+--+-- > [0.12067753390401374,0.8658877349182655,0.7159264443196786,0.1756941896012891,0.9513646060896676]+effectful :: IO a                 -- ^ the action to be executed repeatedly           -> SignalGen (Signal a)-effectful gen = SG $ \pool -> do+effectful act = SG $ \pool -> do   ref <- newIORef (Ready undefined)-  act <- gen    let sample = act >>= memoise ref    addSignal (const sample) (const (() <$ sample)) ref pool  -- | A signal that executes a parametric IO action once at every--- sampling.  The IO action is constructed by an initialiser, and the--- parameter is supplied by another signal at every sampling step.-effectful1 :: IO (t -> IO a)        -- ^ initialiser that yields the action to be executed repeatedly-           -> Signal t              -- ^ parameter signal+-- sampling.  The parameter is supplied by another signal at every+-- sampling step.+effectful1 :: (t -> IO a)          -- ^ the action to be executed repeatedly+           -> Signal t             -- ^ parameter signal            -> SignalGen (Signal a)-effectful1 gen (S s) = SG $ \pool -> do+effectful1 act (S s) = SG $ \pool -> do   ref <- newIORef (Ready undefined)-  act <- gen    let sample = s >>= act >>= memoise ref    addSignal (const sample) (const (() <$ sample)) ref pool  -- | Like 'effectful1', but with two parameter signals.-effectful2 :: IO (t1 -> t2 -> IO a)  -- ^ initialiser that yields the action to be executed repeatedly-           -> Signal t1              -- ^ parameter signal 1-           -> Signal t2              -- ^ parameter signal 2+effectful2 :: (t1 -> t2 -> IO a)   -- ^ the action to be executed repeatedly+           -> Signal t1            -- ^ parameter signal 1+           -> Signal t2            -- ^ parameter signal 2            -> SignalGen (Signal a)-effectful2 gen (S s1) (S s2) = SG $ \pool -> do+effectful2 act (S s1) (S s2) = SG $ \pool -> do   ref <- newIORef (Ready undefined)-  act <- gen    let sample = join (liftM2 act s1 s2) >>= memoise ref    addSignal (const sample) (const (() <$ sample)) ref pool  -- | Like 'effectful1', but with three parameter signals.-effectful3 :: IO (t1 -> t2 -> t3 -> IO a)  -- ^ initialiser that yields the action to be executed repeatedly-           -> Signal t1                    -- ^ parameter signal 1-           -> Signal t2                    -- ^ parameter signal 2-           -> Signal t3                    -- ^ parameter signal 3+effectful3 :: (t1 -> t2 -> t3 -> IO a) -- ^ the action to be executed repeatedly+           -> Signal t1                -- ^ parameter signal 1+           -> Signal t2                -- ^ parameter signal 2+           -> Signal t3                -- ^ parameter signal 3            -> SignalGen (Signal a)-effectful3 gen (S s1) (S s2) (S s3) = SG $ \pool -> do+effectful3 act (S s1) (S s2) (S s3) = SG $ \pool -> do   ref <- newIORef (Ready undefined)-  act <- gen    let sample = join (liftM3 act s1 s2 s3) >>= memoise ref    addSignal (const sample) (const (() <$ sample)) ref pool  -- | Like 'effectful1', but with four parameter signals.-effectful4 :: IO (t1 -> t2 -> t3 -> t4 -> IO a)  -- ^ initialiser that yields the action to be executed repeatedly-           -> Signal t1                          -- ^ parameter signal 1-           -> Signal t2                          -- ^ parameter signal 2-           -> Signal t3                          -- ^ parameter signal 3-           -> Signal t4                          -- ^ parameter signal 4+effectful4 :: (t1 -> t2 -> t3 -> t4 -> IO a) -- ^ the action to be executed repeatedly+           -> Signal t1                      -- ^ parameter signal 1+           -> Signal t2                      -- ^ parameter signal 2+           -> Signal t3                      -- ^ parameter signal 3+           -> Signal t4                      -- ^ parameter signal 4            -> SignalGen (Signal a)-effectful4 gen (S s1) (S s2) (S s3) (S s4) = SG $ \pool -> do+effectful4 act (S s1) (S s2) (S s3) (S s4) = SG $ \pool -> do   ref <- newIORef (Ready undefined)-  act <- gen    let sample = join (liftM4 act s1 s2 s3 s4) >>= memoise ref    addSignal (const sample) (const (() <$ sample)) ref pool---- | A random signal.------ Example:------ > do--- >     smp <- start noise :: IO (IO Double)--- >     res <- replicateM 5 smp--- >     print res------ Output:------ > [0.12067753390401374,0.8658877349182655,0.7159264443196786,0.1756941896012891,0.9513646060896676]-noise :: MTRandom a => SignalGen (Signal a)-noise = memo (S randomIO)---- | A random source within the 'SignalGen' monad.-getRandom :: MTRandom a => SignalGen a-getRandom = SG (const randomIO)---- | A printing action within the 'SignalGen' monad.-debug :: String -> SignalGen ()-debug = SG . const . putStrLn  -- | The Show instance is only defined for the sake of Num... instance Show (Signal a) where
elerea.cabal view
@@ -1,5 +1,5 @@ Name:                elerea-Version:             2.4.0+Version:             2.5.0 Cabal-Version:       >= 1.2 Synopsis:            A minimalistic FRP library Category:            reactivity, FRP@@ -50,13 +50,9 @@ Library   Exposed-Modules:     FRP.Elerea-    FRP.Elerea.Legacy-    FRP.Elerea.Legacy.Graph-    FRP.Elerea.Legacy.Internal-    FRP.Elerea.Legacy.Delayed     FRP.Elerea.Simple     FRP.Elerea.Param     FRP.Elerea.Clocked -  Build-Depends:       base >= 4 && < 5, containers, mersenne-random+  Build-Depends:       base >= 4 && < 5, containers   ghc-options:         -Wall -O2