elerea 2.0.0 → 2.1.0
raw patch · 7 files changed
+250/−128 lines, 7 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- FRP.Elerea.Clocked: instance (Bounded t) => Bounded (Signal t)
- FRP.Elerea.Clocked: instance (Enum t) => Enum (Signal t)
- FRP.Elerea.Clocked: instance (Floating t) => Floating (Signal t)
- FRP.Elerea.Clocked: instance (Fractional t) => Fractional (Signal t)
- FRP.Elerea.Clocked: instance (Integral t) => Integral (Signal t)
- FRP.Elerea.Clocked: instance (Num t) => Num (Signal t)
- FRP.Elerea.Clocked: instance (Ord t) => Ord (Signal t)
- FRP.Elerea.Clocked: instance (Real t) => Real (Signal t)
- FRP.Elerea.Delayed: instance (Bounded t) => Bounded (Signal p t)
- FRP.Elerea.Delayed: instance (Enum t) => Enum (Signal p t)
- FRP.Elerea.Delayed: instance (Floating t) => Floating (Signal p t)
- FRP.Elerea.Delayed: instance (Fractional t) => Fractional (Signal p t)
- FRP.Elerea.Delayed: instance (Integral t) => Integral (Signal p t)
- FRP.Elerea.Delayed: instance (Num t) => Num (Signal p t)
- FRP.Elerea.Delayed: instance (Ord t) => Ord (Signal p t)
- FRP.Elerea.Delayed: instance (Real t) => Real (Signal p t)
- 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 (Floating t) => Floating (Signal t)
- FRP.Elerea.Legacy.Internal: instance (Fractional t) => Fractional (Signal t)
- FRP.Elerea.Legacy.Internal: instance (Integral t) => Integral (Signal t)
- 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.Param: instance (Bounded t) => Bounded (Signal p t)
- FRP.Elerea.Param: instance (Enum t) => Enum (Signal p t)
- FRP.Elerea.Param: instance (Floating t) => Floating (Signal p t)
- FRP.Elerea.Param: instance (Fractional t) => Fractional (Signal p t)
- FRP.Elerea.Param: instance (Integral t) => Integral (Signal p t)
- FRP.Elerea.Param: instance (Num t) => Num (Signal p t)
- FRP.Elerea.Param: instance (Ord t) => Ord (Signal p t)
- FRP.Elerea.Param: instance (Real t) => Real (Signal p t)
- FRP.Elerea.Param: instance Applicative (Signal p)
- FRP.Elerea.Param: instance Eq (Signal p a)
- FRP.Elerea.Param: instance Functor (Signal p)
- FRP.Elerea.Param: instance Monad (Signal p)
- FRP.Elerea.Param: instance Show (Signal p a)
- FRP.Elerea.Simple: instance (Bounded t) => Bounded (Signal t)
- FRP.Elerea.Simple: instance (Enum t) => Enum (Signal t)
- FRP.Elerea.Simple: instance (Floating t) => Floating (Signal t)
- FRP.Elerea.Simple: instance (Fractional t) => Fractional (Signal t)
- FRP.Elerea.Simple: instance (Integral t) => Integral (Signal t)
- FRP.Elerea.Simple: instance (Num t) => Num (Signal t)
- FRP.Elerea.Simple: instance (Ord t) => Ord (Signal t)
- FRP.Elerea.Simple: instance (Real t) => Real (Signal t)
+ FRP.Elerea.Clocked: debug :: String -> SignalGen ()
+ FRP.Elerea.Clocked: instance Bounded t => Bounded (Signal t)
+ FRP.Elerea.Clocked: instance Enum t => Enum (Signal t)
+ FRP.Elerea.Clocked: instance Floating t => Floating (Signal t)
+ FRP.Elerea.Clocked: instance Fractional t => Fractional (Signal t)
+ FRP.Elerea.Clocked: instance Integral t => Integral (Signal t)
+ FRP.Elerea.Clocked: instance Num t => Num (Signal t)
+ FRP.Elerea.Clocked: instance Ord t => Ord (Signal t)
+ FRP.Elerea.Clocked: instance Real t => Real (Signal t)
+ FRP.Elerea.Clocked: until :: Signal Bool -> SignalGen (Signal Bool)
+ FRP.Elerea.Delayed: instance Bounded t => Bounded (Signal p t)
+ FRP.Elerea.Delayed: instance Enum t => Enum (Signal p t)
+ FRP.Elerea.Delayed: instance Floating t => Floating (Signal p t)
+ FRP.Elerea.Delayed: instance Fractional t => Fractional (Signal p t)
+ FRP.Elerea.Delayed: instance Integral t => Integral (Signal p t)
+ FRP.Elerea.Delayed: instance Num t => Num (Signal p t)
+ FRP.Elerea.Delayed: instance Ord t => Ord (Signal p t)
+ FRP.Elerea.Delayed: instance Real t => Real (Signal p t)
+ 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 Floating t => Floating (Signal t)
+ FRP.Elerea.Legacy.Internal: instance Fractional t => Fractional (Signal t)
+ FRP.Elerea.Legacy.Internal: instance Integral t => Integral (Signal t)
+ 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.Param: embed :: Signal p' -> SignalGen p' a -> SignalGen p a
+ FRP.Elerea.Param: input :: SignalGen p (Signal p)
+ FRP.Elerea.Param: instance Applicative Signal
+ FRP.Elerea.Param: instance Bounded t => Bounded (Signal t)
+ FRP.Elerea.Param: instance Enum t => Enum (Signal t)
+ FRP.Elerea.Param: instance Eq (Signal a)
+ FRP.Elerea.Param: instance Floating t => Floating (Signal t)
+ FRP.Elerea.Param: instance Fractional t => Fractional (Signal t)
+ FRP.Elerea.Param: instance Functor Signal
+ FRP.Elerea.Param: instance Integral t => Integral (Signal t)
+ FRP.Elerea.Param: instance Monad Signal
+ FRP.Elerea.Param: instance Num t => Num (Signal t)
+ FRP.Elerea.Param: instance Ord t => Ord (Signal t)
+ FRP.Elerea.Param: instance Real t => Real (Signal t)
+ FRP.Elerea.Param: instance Show (Signal a)
+ FRP.Elerea.Param: until :: Signal Bool -> SignalGen p (Signal Bool)
+ FRP.Elerea.Simple: debug :: String -> SignalGen ()
+ FRP.Elerea.Simple: instance Bounded t => Bounded (Signal t)
+ FRP.Elerea.Simple: instance Enum t => Enum (Signal t)
+ FRP.Elerea.Simple: instance Floating t => Floating (Signal t)
+ FRP.Elerea.Simple: instance Fractional t => Fractional (Signal t)
+ FRP.Elerea.Simple: instance Integral t => Integral (Signal t)
+ FRP.Elerea.Simple: instance Num t => Num (Signal t)
+ FRP.Elerea.Simple: instance Ord t => Ord (Signal t)
+ FRP.Elerea.Simple: instance Real t => Real (Signal t)
+ FRP.Elerea.Simple: until :: Signal Bool -> SignalGen (Signal Bool)
- FRP.Elerea.Clocked: getRandom :: (MTRandom a) => SignalGen a
+ FRP.Elerea.Clocked: getRandom :: MTRandom a => SignalGen a
- FRP.Elerea.Clocked: noise :: (MTRandom a) => SignalGen (Signal a)
+ FRP.Elerea.Clocked: noise :: MTRandom a => SignalGen (Signal a)
- FRP.Elerea.Delayed: getRandom :: (MTRandom a) => SignalGen p a
+ FRP.Elerea.Delayed: getRandom :: MTRandom a => SignalGen p a
- FRP.Elerea.Delayed: noise :: (MTRandom a) => SignalGen p (Signal p a)
+ FRP.Elerea.Delayed: noise :: MTRandom a => SignalGen p (Signal p a)
- FRP.Elerea.Legacy: (/=@) :: (Eq 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: (<@) :: (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: (==@) :: 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: (>@) :: (Ord a) => Signal a -> Signal a -> Signal Bool
+ FRP.Elerea.Legacy: (>@) :: Ord a => Signal a -> Signal a -> Signal Bool
- FRP.Elerea.Legacy: signalDebug :: (Show a) => a -> SignalMonad ()
+ FRP.Elerea.Legacy: signalDebug :: Show a => a -> SignalMonad ()
- FRP.Elerea.Legacy.Internal: signalDebug :: (Show a) => a -> SignalMonad ()
+ FRP.Elerea.Legacy.Internal: signalDebug :: Show a => a -> SignalMonad ()
- FRP.Elerea.Param: data Signal p a
+ FRP.Elerea.Param: data Signal a
- FRP.Elerea.Param: delay :: a -> Signal p a -> SignalGen p (Signal p a)
+ FRP.Elerea.Param: delay :: a -> Signal a -> SignalGen p (Signal a)
- FRP.Elerea.Param: external :: a -> IO (Signal p a, a -> IO ())
+ FRP.Elerea.Param: external :: a -> IO (Signal a, a -> IO ())
- FRP.Elerea.Param: externalMulti :: IO (SignalGen p (Signal p [a]), a -> IO ())
+ FRP.Elerea.Param: externalMulti :: IO (SignalGen p (Signal [a]), a -> IO ())
- FRP.Elerea.Param: generator :: Signal p (SignalGen p a) -> SignalGen p (Signal p a)
+ FRP.Elerea.Param: generator :: Signal (SignalGen p a) -> SignalGen p (Signal a)
- FRP.Elerea.Param: getRandom :: (MTRandom a) => SignalGen p a
+ FRP.Elerea.Param: getRandom :: MTRandom a => SignalGen p a
- FRP.Elerea.Param: memo :: Signal p a -> SignalGen p (Signal p a)
+ FRP.Elerea.Param: memo :: Signal a -> SignalGen p (Signal a)
- FRP.Elerea.Param: noise :: (MTRandom a) => SignalGen p (Signal p a)
+ FRP.Elerea.Param: noise :: MTRandom a => SignalGen p (Signal a)
- FRP.Elerea.Param: start :: SignalGen p (Signal p a) -> IO (p -> IO a)
+ FRP.Elerea.Param: start :: SignalGen p (Signal a) -> IO (p -> IO a)
- FRP.Elerea.Param: stateful :: a -> (p -> a -> a) -> SignalGen p (Signal p a)
+ FRP.Elerea.Param: stateful :: a -> (p -> a -> a) -> SignalGen p (Signal a)
- FRP.Elerea.Param: transfer :: a -> (p -> t -> a -> a) -> Signal p t -> SignalGen p (Signal p a)
+ FRP.Elerea.Param: transfer :: a -> (p -> t -> a -> a) -> Signal t -> SignalGen p (Signal a)
- FRP.Elerea.Simple: getRandom :: (MTRandom a) => SignalGen a
+ FRP.Elerea.Simple: getRandom :: MTRandom a => SignalGen a
- FRP.Elerea.Simple: noise :: (MTRandom a) => SignalGen (Signal a)
+ FRP.Elerea.Simple: noise :: MTRandom a => SignalGen (Signal a)
Files
- CHANGES +22/−7
- FRP/Elerea/Clocked.hs +31/−2
- FRP/Elerea/Delayed.hs +8/−2
- FRP/Elerea/Legacy.hs +5/−6
- FRP/Elerea/Param.hs +128/−103
- FRP/Elerea/Simple.hs +37/−2
- elerea.cabal +19/−6
CHANGES view
@@ -1,10 +1,23 @@+2.1.0 - 100805+* reimplemented the parametric variant in a way that doesn't require+ signals to carry the type of the parameter any more+* added the ability to extract the global input in the parametric+ variant, and also to override it (input and embed, resp.)+* added until to be able to define switchers that can truly drop+ references to old signals+* added debug printing capability to the simple and clocked variants+* made a note about possibly deprecating the delayed variant+ 2.0.0 - 100718-* moved experimental branch to the top (version 1 went into legacy status)-* added the clocked version+* moved experimental branch to the top (version 1 went into legacy+ status)+* added the clocked variant 1.2.3 - 100131-* added externalMulti to handle events that can fire several times within a superstep-* added a cache to the noise signal for safety reasons, so it lives in SignalGen now+* added externalMulti to handle events that can fire several times+ within a superstep+* added a cache to the noise signal for safety reasons, so it lives in+ SignalGen now 1.2.2 - 100115 * added noise signals and the getRandom primitive (using mersenne-random)@@ -13,7 +26,7 @@ * modified the &&@ and ||@ operators to short-circuit 1.2.0 - 091202-* added the delayed version to the experimental branch+* added the delayed variant to the experimental branch * renamed storeJust to (-->) in the experimental branch 1.1.0 - 091126@@ -42,8 +55,10 @@ 0.2.0 - 090412 * removed primitives time and stateless-* removed default delay on stateful combinators and added experimental cycle detection-* added some non-primitive combinators: delay, edge, comparisons, logic relations+* removed default delay on stateful combinators and added experimental+ cycle detection+* added some non-primitive combinators: delay, edge, comparisons,+ logic relations * added signal instances for various numeric classes 0.1.0 - 090410
FRP/Elerea/Clocked.hs view
@@ -3,7 +3,7 @@ {-| This version differs from the simple one in adding associated freeze-control signals ("clocks") to stateful entities to be able to pause+control signals (\'clocks\') to stateful entities to be able to pause entire subnetworks without having to write all the low-level logic explicitly. The clocks are fixed to signals upon their creation, and the 'withClock' function can be used to specify the common clock for@@ -84,12 +84,14 @@ , externalMulti , delay , generator- , withClock , memo+ , until+ , withClock , stateful , transfer , noise , getRandom+ , debug ) where import Control.Applicative@@ -98,6 +100,7 @@ import Control.Monad.Fix import Data.IORef import Data.Maybe+import Prelude hiding (until) import System.Mem.Weak import System.Random.Mersenne @@ -252,6 +255,28 @@ addSignal (const sample) (const (sample >> return ())) 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+-- no reference to the input signal. Note that 'until' always follows+-- the master clock, i.e. the fastest one, therefore it never creates+-- a long spike of @True@.+until :: Signal Bool -- ^ the boolean input signal+ -> SignalGen (Signal Bool) -- ^ a one-shot signal true only the first time the input is true+until (S s) = SG $ \pool _ -> do+ ref <- newIORef (Ready undefined)++ rsmp <- mfix $ \rs -> newIORef $ do+ x <- s+ writeIORef ref (Updated undefined x)+ when x $ writeIORef rs $ do+ writeIORef ref (Updated undefined False)+ return False+ return x++ let sample = join (readIORef rsmp)++ addSignal (const sample) (const (() <$ sample)) 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.@@ -318,6 +343,10 @@ -- | A random source within the 'SignalGen' monad. getRandom :: MTRandom a => SignalGen a getRandom = SG (const (const randomIO))++-- | A printing action within the 'SignalGen' monad.+debug :: String -> SignalGen ()+debug = SG . const . const . putStrLn -- The Show instance is only defined for the sake of Num... instance Show (Signal a) where
FRP/Elerea/Delayed.hs view
@@ -1,9 +1,15 @@ {-| -This version differs from the parametric one in introducing autmatic+Note: this module is likely to be deprecated in the near future,+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.+functions are not delayed by default. Also, the until construct is+missing from this module. The interface of this module differs from the old Elerea in the following ways:
FRP/Elerea/Legacy.hs view
@@ -27,13 +27,12 @@ integral x0 s = transfer x0 (\\dt x x0 -> x0+x*realToFrac dt) s @ -Head to "FRP.Elerea.Internal" for the implementation details. To get-a general idea how to use the library, check out the sources in the-@elerea-examples@ package.+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.Experimental" branch provides a similar interface with-a rather different underlying structure, which is likely to be more-efficient.+The "FRP.Elerea" branch provides a similar interface with a rather+different underlying structure, which is likely to be more efficient. -}
FRP/Elerea/Param.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+ {-| This version differs from the simple one in providing an extra@@ -26,8 +28,11 @@ (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.+ reused in multiple places explicitly using the 'memo' combinator; +* the input can be retrieved as an explicit signal within the+ SignalGen monad, and also overridden for parts of the network.+ -} module FRP.Elerea.Param@@ -37,10 +42,13 @@ , external , externalMulti , delay+ , generator+ , memo+ , until+ , input+ , embed , stateful , transfer- , memo- , generator , noise , getRandom , debug@@ -52,38 +60,28 @@ import Control.Monad.Fix import Data.IORef import Data.Maybe+import Prelude hiding (until) 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 }+newtype Signal a = S (IO a) deriving (Functor, Applicative, Monad) -- | A dynamic set of actions to update a network without breaking -- consistency.-type UpdatePool p = [Weak (p -> IO (), IO ())]+type UpdatePool = [Weak (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 }+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 -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 @@ -92,11 +90,11 @@ (<*>) = ap instance Monad (SignalGen p) where- return = SG . const . return- SG g >>= f = SG $ \p -> g p >>= \x -> unSG (f x) p+ return = SG . const . const . return+ SG g >>= f = SG $ \p i -> g p i >>= \x -> unSG (f x) p i instance MonadFix (SignalGen p) where- mfix f = SG $ \p -> mfix (($p).unSG.f)+ mfix f = SG $ \p i -> mfix (($i).($p).unSG.f) -- | Embedding a signal into an 'IO' environment. Repeated calls to -- the computation returned cause the whole network to be updated, and@@ -104,48 +102,50 @@ -- 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 :: SignalGen p (Signal 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+ (inp,sink) <- external undefined+ S sample <- gen pool inp ptrs0 <- readIORef pool writeIORef pool [] (as0,cs0) <- unzip . map fromJust <$> mapM deRefWeak ptrs0- let ageStatic param = mapM_ ($param) as0+ let ageStatic = sequence_ as0 commitStatic = sequence_ cs0 return $ \param -> do let update [] ptrs age commit = do writeIORef pool ptrs- ageStatic param >> age+ 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 param) (commit >> c)+ Just (a,c) -> update ps (p:ptrs) (age >> a) (commit >> c) - res <- sample param+ sink param+ res <- sample 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 :: (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+ -> IO (Signal 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+ sig = S $ readIORef ref >>= sample - update <- mkWeak sig (\p -> readIORef ref >>= age p, modifyIORef ref commit) Nothing+ update <- mkWeak sig (readIORef ref >>= age, modifyIORef ref commit) Nothing modifyIORef pool (update:) return sig @@ -153,63 +153,92 @@ -- 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+ -> Signal a -- ^ the signal to delay+ -> SignalGen p (Signal a)+delay x0 (S s) = SG $ \pool _ -> do ref <- newIORef (Ready x0) - let sample _ (Ready x) = return x- sample _ (Aged _ x) = return x+ let sample (Ready x) = return x+ sample (Aged _ x) = return x - age p (Ready x) = s p >>= \x' -> x' `seq` writeIORef ref (Aged x' x)- age _ _ = return ()+ 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. 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+memo :: Signal a -- ^ signal to memoise+ -> SignalGen p (Signal 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+ let sample (Ready _) = s >>= \x -> writeIORef ref (Aged undefined x) >> return x+ sample (Aged _ x) = return x - age p (Ready _) = s p >>= \x -> writeIORef ref (Aged undefined x)- age _ _ = return ()+ age (Ready _) = s >>= \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+generator :: Signal (SignalGen p a) -- ^ a stream of generators to potentially run+ -> SignalGen p (Signal a)+generator (S gen) = SG $ \pool inp -> do ref <- newIORef (Ready undefined) - let next p = ($pool).unSG =<< gen p+ let next = ($inp).($pool).unSG =<< gen - sample p (Ready _) = next p >>= \x' -> writeIORef ref (Aged x' x') >> return x'- sample _ (Aged _ x) = return x+ sample (Ready _) = next >>= \x' -> writeIORef ref (Aged x' x') >> return x'+ sample (Aged _ x) = return x - age p (Ready _) = next p >>= \x' -> writeIORef ref (Aged x' x')- age _ _ = return ()+ age (Ready _) = next >>= \x' -> writeIORef ref (Aged x' x')+ age _ = return () addSignal sample age 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+-- no reference to the input signal.+until :: Signal Bool -- ^ the boolean input signal+ -> SignalGen p (Signal Bool) -- ^ a one-shot signal true only the first time the input is true+until (S s) = SG $ \pool _ -> do+ ref <- newIORef (Ready undefined)++ rsmp <- mfix $ \rs -> newIORef $ do+ x <- s+ writeIORef ref (Aged undefined x)+ when x $ writeIORef rs $ do+ writeIORef ref (Aged undefined False)+ return False+ return x++ let sample = join (readIORef rsmp)++ addSignal (const sample) (const (() <$ sample)) ref pool++-- | The common input signal that is fed through the function returned+-- by 'start', unless we are in an 'embed'ded generator.+input :: SignalGen p (Signal p)+input = SG $ const return++-- | Embed a generator with an overridden input signal.+embed :: Signal p' -> SignalGen p' a -> SignalGen p a+embed s (SG g) = SG $ \pool _ -> g pool s+ -- | 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 :: a -- ^ initial value+ -> IO (Signal 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)+ return (S (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@@ -219,12 +248,12 @@ -- '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 :: IO (SignalGen p (Signal [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+ return (SG $ \pool _ -> do+ let sig = S $ readMVar var+ update <- mkWeak sig (return (),takeMVar var >> putMVar var []) Nothing modifyIORef pool (update:) return sig ,\val -> do vals <- takeMVar var@@ -233,68 +262,64 @@ -- | 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-- age p (Ready x) = let x' = f p x in x' `seq` writeIORef ref (Aged x' x)- age _ _ = return ()-- addSignal sample age ref pool+-- the value of the global parameter (which might have been overridden+-- by 'embed'). It is equivalent to the following expression:+--+-- @+-- stateful x0 f = 'mfix' $ \sig -> 'input' >>= \i -> 'delay' x0 (f '<$>' i '<*>' sig)+-- @+stateful :: a -- ^ initial state+ -> (p -> a -> a) -- ^ state transformation+ -> SignalGen p (Signal a)+stateful x0 f = mfix $ \sig -> input >>= \i -> delay x0 (f <$> i <*> sig) -- | 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.-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) = s p >>= \y -> let x' = f p y x in- x' `seq` writeIORef ref (Aged x' x') >> return x'- sample _ (Aged _ x) = return x-- age p (Ready x) = s p >>= \y -> let x' = f p y x in- x' `seq` writeIORef ref (Aged x' x')- age _ _ = return ()-- addSignal sample age ref pool+-- input signal, the global parameter (which might have been+-- overridden by 'embed') and the previous output. It is equivalent+-- to the following expression:+--+-- @+-- transfer x0 f s = 'mfix' $ \sig -> 'input' >>= \i -> 'liftA3' f i s '<$>' 'delay' x0 sig+-- @+transfer :: a -- ^ initial internal state+ -> (p -> t -> a -> a) -- ^ state updater function+ -> Signal t -- ^ input signal+ -> SignalGen p (Signal a)+transfer x0 f s = mfix $ \sig -> input >>= \i -> liftA3 f i s <$> delay x0 sig -- | A random signal.-noise :: MTRandom a => SignalGen p (Signal p a)-noise = memo (S (const randomIO))+noise :: MTRandom a => SignalGen p (Signal a)+noise = memo (S randomIO) -- | A random source within the 'SignalGen' monad. getRandom :: MTRandom a => SignalGen p a-getRandom = SG (const randomIO)+getRandom = SG (const (const randomIO)) -- | A printing action within the 'SignalGen' monad. debug :: String -> SignalGen p ()-debug = SG . const . putStrLn+debug = SG . const . const . putStrLn -- | The @Show@ instance is only defined for the sake of 'Num'...-instance Show (Signal p a) where+instance Show (Signal a) where showsPrec _ _ s = "<SIGNAL>" ++ s -- | Equality test is impossible.-instance Eq (Signal p a) where+instance Eq (Signal a) where _ == _ = False -- | Error message for unimplemented instance functions. unimp :: String -> a unimp = error . ("Signal: "++) -instance Ord t => Ord (Signal p t) where+instance Ord t => Ord (Signal t) where compare = unimp "compare" min = liftA2 min max = liftA2 max -instance Enum t => Enum (Signal p t) where+instance Enum t => Enum (Signal t) where succ = fmap succ pred = fmap pred toEnum = pure . toEnum@@ -304,11 +329,11 @@ enumFromTo = unimp "enumFromTo" enumFromThenTo = unimp "enumFromThenTo" -instance Bounded t => Bounded (Signal p t) where+instance Bounded t => Bounded (Signal t) where minBound = pure minBound maxBound = pure maxBound -instance Num t => Num (Signal p t) where+instance Num t => Num (Signal t) where (+) = liftA2 (+) (-) = liftA2 (-) (*) = liftA2 (*)@@ -317,10 +342,10 @@ negate = fmap negate fromInteger = pure . fromInteger -instance Real t => Real (Signal p t) where+instance Real t => Real (Signal t) where toRational = unimp "toRational" -instance Integral t => Integral (Signal p t) where+instance Integral t => Integral (Signal t) where quot = liftA2 quot rem = liftA2 rem div = liftA2 div@@ -331,12 +356,12 @@ where dmab = divMod <$> a <*> b toInteger = unimp "toInteger" -instance Fractional t => Fractional (Signal p t) where+instance Fractional t => Fractional (Signal t) where (/) = liftA2 (/) recip = fmap recip fromRational = pure . fromRational -instance Floating t => Floating (Signal p t) where+instance Floating t => Floating (Signal t) where pi = pure pi exp = fmap exp sqrt = fmap sqrt
FRP/Elerea/Simple.hs view
@@ -131,10 +131,12 @@ , delay , generator , memo+ , until , stateful , transfer , noise , getRandom+ , debug ) where import Control.Applicative@@ -143,6 +145,7 @@ import Control.Monad.Fix import Data.IORef import Data.Maybe+import Prelude hiding (until) import System.Mem.Weak import System.Random.Mersenne @@ -273,7 +276,7 @@ writeIORef ref (Updated undefined x) return x - addSignal (const sample) (const (sample >> return ())) ref pool+ addSignal (const sample) (const (() <$ sample)) ref pool -- | Memoising combinator. It can be used to cache results of -- applicative combinators in case they are used in several places.@@ -286,8 +289,36 @@ let sample = s >>= \x -> writeIORef ref (Updated undefined x) >> return x - addSignal (const sample) (const (sample >> return ())) 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+-- no reference to the input signal. It is observationally equivalent+-- to the following expression (which would hold onto @s@ forever):+--+-- @+-- until s = do+-- step <- 'transfer' False (||) s+-- dstep <- 'delay' False step+-- return $ 'liftA2' (/=) step dstep+-- @+until :: Signal Bool -- ^ the boolean input signal+ -> SignalGen (Signal Bool) -- ^ a one-shot signal true only the first time the input is true+until (S s) = SG $ \pool -> do+ ref <- newIORef (Ready undefined)++ rsmp <- mfix $ \rs -> newIORef $ do+ x <- s+ writeIORef ref (Updated undefined x)+ when x $ writeIORef rs $ do+ writeIORef ref (Updated undefined False)+ return False+ return x++ let sample = join (readIORef rsmp)++ addSignal (const sample) (const (() <$ sample)) 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.@@ -354,6 +385,10 @@ -- | 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,11 +1,11 @@ Name: elerea-Version: 2.0.0+Version: 2.1.0 Cabal-Version: >= 1.2 Synopsis: A minimalistic FRP library Category: reactivity, FRP Description: - Elerea (Eventless reactivity) is a tiny continuous-time FRP+ Elerea (Eventless reactivity) is a tiny discrete time FRP implementation without the notion of event-based switching and sampling, with first-class signals (time-varying values). Reactivity is provided through various higher-order constructs that also allow@@ -15,15 +15,28 @@ Stateful signals can be safely generated at any time through a specialised monad, while stateless combinators can be used in a purely applicative style. Elerea signals can be defined recursively,- and external input is trivial to attach. A unique feature of the- library is that cyclic dependencies are detected on the fly and- resolved by inserting delays dynamically, unless the user does it- explicitly.+ and external input is trivial to attach. The library comes in four+ major variants: .+ * Simple: signals are plain discrete streams isomorphic to functions+ over natural numbers;+ .+ * Param: adds a globally accessible input signal for convenience;+ .+ * Clocked: adds the ability to freeze whole subnetworks at will;+ .+ * Delayed: attempts to resolve instantaneous dependency cycles+ (i.e. cycles without a delay); this variant is likely to be+ deprecated in the near future.+ .+ The first three variants come with precise denotational semantics.+ . This is a minimal library that defines only some basic primitives, and you are advised to install @elerea-examples@ as well to get an idea how to build non-trivial systems with it. The examples are separated in order to minimise the dependencies of the core library.+ The @dow@ package contains a full game built on top of the simple+ variant. Author: Patai Gergely Maintainer: Patai Gergely (patai@iit.bme.hu)