bearriver 0.14.9 → 0.14.10
raw patch · 8 files changed
+638/−123 lines, 8 filesdep +randomdep ~MonadRandomdep ~mtlPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: random
Dependency ranges changed: MonadRandom, mtl
API changes (from Hackage documentation)
- FRP.BearRiver: evalAt :: SF Identity a b -> DTime -> a -> (b, SF Identity a b)
- FRP.BearRiver: evalAtZero :: SF Identity a b -> a -> (b, SF Identity a b)
- FRP.BearRiver: evalFuture :: SF Identity a b -> a -> DTime -> (b, SF Identity a b)
- FRP.BearRiver: occasionally :: MonadRandom m => Time -> b -> SF m a (Event b)
- FRP.BearRiver: reactimate :: Monad m => m a -> (Bool -> m (DTime, Maybe a)) -> (Bool -> b -> m Bool) -> SF Identity a b -> m ()
+ FRP.BearRiver.Integration: count :: (Integral b, Monad m) => SF m (Event a) (Event b)
+ FRP.BearRiver.Integration: imIntegral :: (Fractional s, VectorSpace a s, Monad m) => a -> SF m a a
+ FRP.BearRiver.Integration: impulseIntegral :: (Fractional k, VectorSpace a k, Monad m) => SF m (a, Event a) a
+ FRP.BearRiver.Integration: trapezoidIntegral :: (Fractional s, VectorSpace a s, Monad m) => SF m a a
+ FRP.BearRiver.Random: class () => Random a
+ FRP.BearRiver.Random: class () => RandomGen g
+ FRP.BearRiver.Random: genRange :: RandomGen g => g -> (Int, Int)
+ FRP.BearRiver.Random: genShortByteString :: RandomGen g => Int -> g -> (ShortByteString, g)
+ FRP.BearRiver.Random: genWord16 :: RandomGen g => g -> (Word16, g)
+ FRP.BearRiver.Random: genWord32 :: RandomGen g => g -> (Word32, g)
+ FRP.BearRiver.Random: genWord32R :: RandomGen g => Word32 -> g -> (Word32, g)
+ FRP.BearRiver.Random: genWord64 :: RandomGen g => g -> (Word64, g)
+ FRP.BearRiver.Random: genWord64R :: RandomGen g => Word64 -> g -> (Word64, g)
+ FRP.BearRiver.Random: genWord8 :: RandomGen g => g -> (Word8, g)
+ FRP.BearRiver.Random: next :: RandomGen g => g -> (Int, g)
+ FRP.BearRiver.Random: noise :: (RandomGen g, Random b, Monad m) => g -> SF m a b
+ FRP.BearRiver.Random: noiseR :: (RandomGen g, Random b, Monad m) => (b, b) -> g -> SF m a b
+ FRP.BearRiver.Random: occasionally :: (RandomGen g, Monad m) => g -> Time -> b -> SF m a (Event b)
+ FRP.BearRiver.Random: random :: (Random a, RandomGen g) => g -> (a, g)
+ FRP.BearRiver.Random: randomR :: (Random a, RandomGen g) => (a, a) -> g -> (a, g)
+ FRP.BearRiver.Random: randomRs :: (Random a, RandomGen g) => (a, a) -> g -> [a]
+ FRP.BearRiver.Random: randoms :: (Random a, RandomGen g) => g -> [a]
+ FRP.BearRiver.Random: split :: RandomGen g => g -> (g, g)
+ FRP.BearRiver.Simulation: data FutureSF m a b
+ FRP.BearRiver.Simulation: data ReactHandle a b
+ FRP.BearRiver.Simulation: deltaEncode :: Eq a => DTime -> [a] -> (a, [(DTime, Maybe a)])
+ FRP.BearRiver.Simulation: deltaEncodeBy :: (a -> a -> Bool) -> DTime -> [a] -> (a, [(DTime, Maybe a)])
+ FRP.BearRiver.Simulation: embed :: Monad m => SF m a b -> (a, [(DTime, Maybe a)]) -> m [b]
+ FRP.BearRiver.Simulation: embedSynch :: forall m a b. (Monad m, MonadFail m) => SF m a b -> (a, [(DTime, Maybe a)]) -> SF m Double b
+ FRP.BearRiver.Simulation: evalAt :: SF Identity a b -> DTime -> a -> (b, SF Identity a b)
+ FRP.BearRiver.Simulation: evalAtZero :: SF Identity a b -> a -> (b, SF Identity a b)
+ FRP.BearRiver.Simulation: evalFuture :: SF Identity a b -> a -> DTime -> (b, SF Identity a b)
+ FRP.BearRiver.Simulation: react :: ReactHandle a b -> (DTime, Maybe a) -> IO Bool
+ FRP.BearRiver.Simulation: reactInit :: IO a -> (ReactHandle a b -> Bool -> b -> IO Bool) -> SF Identity a b -> IO (ReactHandle a b)
+ FRP.BearRiver.Simulation: reactimate :: Monad m => m a -> (Bool -> m (DTime, Maybe a)) -> (Bool -> b -> m Bool) -> SF m a b -> m ()
+ FRP.BearRiver.Task: abortWhen :: Monad m => Task m a b c -> SF m a (Event d) -> Task m a b (Either c d)
+ FRP.BearRiver.Task: constT :: Monad m => b -> Task m a b c
+ FRP.BearRiver.Task: data Task m a b c
+ FRP.BearRiver.Task: infixl 0 `abortWhen`
+ FRP.BearRiver.Task: instance GHC.Base.Monad m => GHC.Base.Applicative (FRP.BearRiver.Task.Task m a b)
+ FRP.BearRiver.Task: instance GHC.Base.Monad m => GHC.Base.Functor (FRP.BearRiver.Task.Task m a b)
+ FRP.BearRiver.Task: instance GHC.Base.Monad m => GHC.Base.Monad (FRP.BearRiver.Task.Task m a b)
+ FRP.BearRiver.Task: mkTask :: Monad m => SF m a (b, Event c) -> Task m a b c
+ FRP.BearRiver.Task: runTask :: Monad m => Task m a b c -> SF m a (Either b c)
+ FRP.BearRiver.Task: runTask_ :: Monad m => Task m a b c -> SF m a b
+ FRP.BearRiver.Task: sleepT :: Monad m => Time -> b -> Task m a b ()
+ FRP.BearRiver.Task: snapT :: Monad m => Task m a b a
+ FRP.BearRiver.Task: taskToSF :: Monad m => Task m a b c -> SF m a (b, Event c)
+ FRP.BearRiver.Task: timeOut :: Monad m => Task m a b c -> Time -> Task m a b (Maybe c)
+ FRP.Yampa: abortWhen :: Monad m => Task m a b c -> SF m a (Event d) -> Task m a b (Either c d)
+ FRP.Yampa: class () => Random a
+ FRP.Yampa: class () => RandomGen g
+ FRP.Yampa: constT :: Monad m => b -> Task m a b c
+ FRP.Yampa: data ReactHandle a b
+ FRP.Yampa: data Task m a b c
+ FRP.Yampa: deltaEncode :: Eq a => DTime -> [a] -> (a, [(DTime, Maybe a)])
+ FRP.Yampa: deltaEncodeBy :: (a -> a -> Bool) -> DTime -> [a] -> (a, [(DTime, Maybe a)])
+ FRP.Yampa: embedSynch :: forall m a b. (Monad m, MonadFail m) => SF m a b -> (a, [(DTime, Maybe a)]) -> SF m Double b
+ FRP.Yampa: genRange :: RandomGen g => g -> (Int, Int)
+ FRP.Yampa: genShortByteString :: RandomGen g => Int -> g -> (ShortByteString, g)
+ FRP.Yampa: genWord16 :: RandomGen g => g -> (Word16, g)
+ FRP.Yampa: genWord32 :: RandomGen g => g -> (Word32, g)
+ FRP.Yampa: genWord32R :: RandomGen g => Word32 -> g -> (Word32, g)
+ FRP.Yampa: genWord64 :: RandomGen g => g -> (Word64, g)
+ FRP.Yampa: genWord64R :: RandomGen g => Word64 -> g -> (Word64, g)
+ FRP.Yampa: genWord8 :: RandomGen g => g -> (Word8, g)
+ FRP.Yampa: imIntegral :: (Fractional s, VectorSpace a s, Monad m) => a -> SF m a a
+ FRP.Yampa: impulseIntegral :: (Fractional k, VectorSpace a k, Monad m) => SF m (a, Event a) a
+ FRP.Yampa: infixl 0 `abortWhen`
+ FRP.Yampa: mkTask :: Monad m => SF m a (b, Event c) -> Task m a b c
+ FRP.Yampa: noise :: (RandomGen g, Random b, Monad m) => g -> SF m a b
+ FRP.Yampa: noiseR :: (RandomGen g, Random b, Monad m) => (b, b) -> g -> SF m a b
+ FRP.Yampa: random :: (Random a, RandomGen g) => g -> (a, g)
+ FRP.Yampa: randomR :: (Random a, RandomGen g) => (a, a) -> g -> (a, g)
+ FRP.Yampa: randomRs :: (Random a, RandomGen g) => (a, a) -> g -> [a]
+ FRP.Yampa: randoms :: (Random a, RandomGen g) => g -> [a]
+ FRP.Yampa: react :: ReactHandle a b -> (DTime, Maybe a) -> IO Bool
+ FRP.Yampa: reactInit :: IO a -> (ReactHandle a b -> Bool -> b -> IO Bool) -> SF Identity a b -> IO (ReactHandle a b)
+ FRP.Yampa: runTask :: Monad m => Task m a b c -> SF m a (Either b c)
+ FRP.Yampa: runTask_ :: Monad m => Task m a b c -> SF m a b
+ FRP.Yampa: sleepT :: Monad m => Time -> b -> Task m a b ()
+ FRP.Yampa: snapT :: Monad m => Task m a b a
+ FRP.Yampa: split :: RandomGen g => g -> (g, g)
+ FRP.Yampa: taskToSF :: Monad m => Task m a b c -> SF m a (b, Event c)
+ FRP.Yampa: timeOut :: Monad m => Task m a b c -> Time -> Task m a b (Maybe c)
+ FRP.Yampa: trapezoidIntegral :: (Fractional s, VectorSpace a s, Monad m) => SF m a a
- FRP.Yampa: count :: forall n (m :: Type -> Type) a. (Num n, Monad m) => MSF m a n
+ FRP.Yampa: count :: (Integral b, Monad m) => SF m (Event a) (Event b)
- FRP.Yampa: embed :: Monad m => MSF m a b -> [a] -> m [b]
+ FRP.Yampa: embed :: Monad m => SF m a b -> (a, [(DTime, Maybe a)]) -> m [b]
- FRP.Yampa: next :: forall (m :: Type -> Type) b a. Monad m => b -> MSF m a b -> MSF m a b
+ FRP.Yampa: next :: RandomGen g => g -> (Int, g)
- FRP.Yampa: occasionally :: MonadRandom m => Time -> b -> SF m a (Event b)
+ FRP.Yampa: occasionally :: (RandomGen g, Monad m) => g -> Time -> b -> SF m a (Event b)
- FRP.Yampa: reactimate :: Monad m => m a -> (Bool -> m (DTime, Maybe a)) -> (Bool -> b -> m Bool) -> SF Identity a b -> m ()
+ FRP.Yampa: reactimate :: Monad m => m a -> (Bool -> m (DTime, Maybe a)) -> (Bool -> b -> m Bool) -> SF m a b -> m ()
Files
- CHANGELOG +10/−0
- bearriver.cabal +6/−3
- src/FRP/BearRiver.hs +7/−115
- src/FRP/BearRiver/Integration.hs +51/−4
- src/FRP/BearRiver/Random.hs +84/−0
- src/FRP/BearRiver/Simulation.hs +292/−0
- src/FRP/BearRiver/Task.hs +187/−0
- src/FRP/Yampa.hs +1/−1
CHANGELOG view
@@ -1,3 +1,13 @@+2024-08-21 Ivan Perez <ivan.perez@keera.co.uk>+ * Version bump (0.14.10) (#430).+ * Offer all definitions from FRP.Yampa.Integration (#422).+ * Offer all definitions from FRP.Yampa.Random (#423).+ * Offer all definitions from FRP.Yampa.Task (#424).+ * Increase upper bounds on mtl (#427).+ * Offer all definitions from FRP.Yampa.Simulation (#425).+ * Implement integral using trapezoid rule (#429).+ * Thanks to @tomsmeding.+ 2024-06-21 Ivan Perez <ivan.perez@keera.co.uk> * Version bump (0.14.9) (#420). * Offer all definitions from FRP.Yampa.Hybrid (#419).
bearriver.cabal view
@@ -30,7 +30,7 @@ build-type: Simple name: bearriver-version: 0.14.9+version: 0.14.10 author: Ivan Perez, Manuel Bärenz maintainer: ivan.perez@keera.co.uk homepage: https://github.com/ivanperez-keera/dunai@@ -88,8 +88,11 @@ FRP.BearRiver.Hybrid FRP.BearRiver.Integration FRP.BearRiver.Loop+ FRP.BearRiver.Random FRP.BearRiver.Scan+ FRP.BearRiver.Simulation FRP.BearRiver.Switches+ FRP.BearRiver.Task FRP.BearRiver.Time FRP.Yampa @@ -100,8 +103,8 @@ base >= 4.6 && <5 , deepseq >= 1.3.0.0 && < 1.6 , dunai >= 0.6.0 && < 0.14- , MonadRandom >= 0.2 && < 0.7- , mtl >= 2.1.2 && < 2.3+ , mtl >= 2.1.2 && < 2.4+ , random >= 1.1 && < 1.3 , simple-affine-space >= 0.1 && < 0.3 , transformers >= 0.3 && < 0.7
src/FRP/BearRiver.hs view
@@ -22,7 +22,6 @@ -- External imports import Control.Arrow as X-import Control.Monad.Random (MonadRandom) import Data.Functor.Identity (Identity (..)) import Data.Maybe (fromMaybe) import Data.VectorSpace as X@@ -30,8 +29,10 @@ -- Internal imports (dunai) import Control.Monad.Trans.MSF hiding (dSwitch) import qualified Control.Monad.Trans.MSF as MSF-import Data.MonadicStreamFunction as X hiding (iPre,- once, reactimate,+import Data.MonadicStreamFunction as X hiding (count,+ embed, iPre,+ next, once,+ reactimate, repeatedly, switch, trace) import qualified Data.MonadicStreamFunction as MSF@@ -45,8 +46,11 @@ import FRP.BearRiver.Hybrid as X import FRP.BearRiver.Integration as X import FRP.BearRiver.InternalCore as X+import FRP.BearRiver.Random as X import FRP.BearRiver.Scan as X+import FRP.BearRiver.Simulation as X import FRP.BearRiver.Switches as X+import FRP.BearRiver.Task as X import FRP.BearRiver.Time as X -- Internal imports (dunai, instances)@@ -77,115 +81,3 @@ -- | Loop with an initial value for the signal being fed back. loopPre :: Monad m => c -> SF m (a, c) (b, c) -> SF m a b loopPre = feedback---- * Noise (random signal) sources and stochastic event sources---- | Stochastic event source with events occurring on average once every tAvg--- seconds. However, no more than one event results from any one sampling--- interval in the case of relatively sparse sampling, thus avoiding an "event--- backlog" should sampling become more frequent at some later point in time.-occasionally :: MonadRandom m- => Time -- ^ The time /q/ after which the event should be produced- -- on average- -> b -- ^ Value to produce at time of event- -> SF m a (Event b)-occasionally tAvg b- | tAvg <= 0- = error "bearriver: Non-positive average interval in occasionally."-- | otherwise = proc _ -> do- r <- getRandomRS (0, 1) -< ()- dt <- timeDelta -< ()- let p = 1 - exp (-(dt / tAvg))- returnA -< if r < p then Event b else NoEvent- where- timeDelta :: Monad m => SF m a DTime- timeDelta = constM ask---- * Execution/simulation---- ** Reactimation---- | Convenience function to run a signal function indefinitely, using a IO--- actions to obtain new input and process the output.------ This function first runs the initialization action, which provides the--- initial input for the signal transformer at time 0.------ Afterwards, an input sensing action is used to obtain new input (if any) and--- the time since the last iteration. The argument to the input sensing--- function indicates if it can block. If no new input is received, it is--- assumed to be the same as in the last iteration.------ After applying the signal function to the input, the actuation IO action is--- executed. The first argument indicates if the output has changed, the second--- gives the actual output). Actuation functions may choose to ignore the first--- argument altogether. This action should return True if the reactimation must--- stop, and False if it should continue.------ Note that this becomes the program's /main loop/, which makes using this--- function incompatible with GLUT, Gtk and other graphics libraries. It may--- also impose a sizeable constraint in larger projects in which different--- subparts run at different time steps. If you need to control the main loop--- yourself for these or other reasons, use 'reactInit' and 'react'.-reactimate :: Monad m- => m a- -> (Bool -> m (DTime, Maybe a))- -> (Bool -> b -> m Bool)- -> SF Identity a b- -> m ()-reactimate senseI sense actuate sf = do- MSF.reactimateB $ senseSF >>> sfIO >>> actuateSF- return ()- where- sfIO = morphS (return.runIdentity) (runReaderS sf)-- -- Sense- senseSF = MSF.dSwitch senseFirst senseRest-- -- Sense: First sample- senseFirst = constM senseI >>> arr (\x -> ((0, x), Just x))-- -- Sense: Remaining samples- senseRest a = constM (sense True) >>> (arr id *** keepLast a)-- keepLast :: Monad m => a -> MSF m (Maybe a) a- keepLast a = MSF $ \ma ->- let a' = fromMaybe a ma- in a' `seq` return (a', keepLast a')-- -- Consume/render- actuateSF = arr (\x -> (True, x)) >>> arrM (uncurry actuate)---- * Debugging / Step by step simulation---- | Evaluate an SF, and return an output and an initialized SF.------ /WARN/: Do not use this function for standard simulation. This function is--- intended only for debugging/testing. Apart from being potentially slower and--- consuming more memory, it also breaks the FRP abstraction by making samples--- discrete and step based.-evalAtZero :: SF Identity a b -> a -> (b, SF Identity a b)-evalAtZero sf a = runIdentity $ runReaderT (unMSF sf a) 0---- | Evaluate an initialized SF, and return an output and a continuation.------ /WARN/: Do not use this function for standard simulation. This function is--- intended only for debugging/testing. Apart from being potentially slower and--- consuming more memory, it also breaks the FRP abstraction by making samples--- discrete and step based.-evalAt :: SF Identity a b -> DTime -> a -> (b, SF Identity a b)-evalAt sf dt a = runIdentity $ runReaderT (unMSF sf a) dt---- | Given a signal function and time delta, it moves the signal function into--- the future, returning a new uninitialized SF and the initial output.------ While the input sample refers to the present, the time delta refers to the--- future (or to the time between the current sample and the next sample).------ /WARN/: Do not use this function for standard simulation. This function is--- intended only for debugging/testing. Apart from being potentially slower and--- consuming more memory, it also breaks the FRP abstraction by making samples--- discrete and step based.-evalFuture :: SF Identity a b -> a -> DTime -> (b, SF Identity a b)-evalFuture sf = flip (evalAt sf)
src/FRP/BearRiver/Integration.hs view
@@ -5,12 +5,31 @@ -- License : BSD3 -- Maintainer : ivan.perez@keera.co.uk ----- Implementation of integrals and derivatives using Monadic Stream Processing--- library.+-- Integration and derivation of input signals.+--+-- In continuous time, these primitives define SFs that integrate/derive the+-- input signal. Since this is subject to the sampling resolution, simple+-- versions are implemented (like the rectangle rule for the integral).+--+-- In discrete time, all we do is count the number of events.+--+-- The combinator 'iterFrom' gives enough flexibility to program your own+-- leak-free integration and derivation SFs.+--+-- Many primitives and combinators in this module require instances of+-- simple-affine-spaces's 'VectorSpace'. BearRiver does not enforce the use of a+-- particular vector space implementation, meaning you could use 'integral' for+-- example with other vector types like V2, V1, etc. from the library linear.+-- For an example, see+-- <https://gist.github.com/walseb/1e0a0ca98aaa9469ab5da04e24f482c2 this gist>. module FRP.BearRiver.Integration ( -- * Integration integral+ , imIntegral+ , trapezoidIntegral+ , impulseIntegral+ , count -- * Differentiation , derivative@@ -19,7 +38,7 @@ where -- External imports-import Control.Arrow (returnA)+import Control.Arrow (returnA, (***), (>>^)) import Data.VectorSpace (VectorSpace, zeroVector, (*^), (^+^), (^-^), (^/)) -- Internal imports (dunai)@@ -28,9 +47,11 @@ import Data.MonadicStreamFunction.InternalCore (MSF (MSF)) -- Internal imports+import FRP.BearRiver.Event (Event)+import FRP.BearRiver.Hybrid (accumBy, accumHoldBy) import FRP.BearRiver.InternalCore (DTime, SF) --- * Integration and differentiation+-- * Integration -- | Integration using the rectangle rule. integral :: (Monad m, Fractional s, VectorSpace a s) => SF m a a@@ -43,6 +64,32 @@ integralFrom a0 = proc a -> do dt <- constM ask -< () accumulateWith (^+^) a0 -< realToFrac dt *^ a++-- | \"Immediate\" integration (using the function's value at the current time).+imIntegral :: (Fractional s, VectorSpace a s, Monad m)+ => a -> SF m a a+imIntegral = ((\_ a' dt v -> v ^+^ realToFrac dt *^ a') `iterFrom`)++-- | Trapezoid integral (using the average between the value at the last time+-- and the value at the current time).+trapezoidIntegral :: (Fractional s, VectorSpace a s, Monad m) => SF m a a+trapezoidIntegral =+ iterFrom (\a a' dt v -> v ^+^ (realToFrac dt / 2) *^ (a ^+^ a')) zeroVector++-- | Integrate the first input signal and add the /discrete/ accumulation (sum)+-- of the second, discrete, input signal.+impulseIntegral :: (Fractional k, VectorSpace a k, Monad m)+ => SF m (a, Event a) a+impulseIntegral = (integral *** accumHoldBy (^+^) zeroVector) >>^ uncurry (^+^)++-- | Count the occurrences of input events.+--+-- >>> embed count (deltaEncode 1 [Event 'a', NoEvent, Event 'b'])+-- [Event 1,NoEvent,Event 2]+count :: (Integral b, Monad m) => SF m (Event a) (Event b)+count = accumBy (\n _ -> n + 1) 0++-- * Differentiation -- | A very crude version of a derivative. It simply divides the value -- difference by the time difference. Use at your own risk.
+ src/FRP/BearRiver/Random.hs view
@@ -0,0 +1,84 @@+-- |+-- Module : FRP.BearRiver.Random+-- Copyright : (c) Ivan Perez, 2014-2024+-- (c) George Giorgidze, 2007-2012+-- (c) Henrik Nilsson, 2005-2006+-- (c) Antony Courtney and Henrik Nilsson, Yale University, 2003-2004+-- License : BSD3+--+-- Maintainer : ivan.perez@keera.co.uk+-- Stability : provisional+-- Portability : non-portable (GHC extensions)+--+-- Signals and signal functions with noise and randomness.+--+-- The Random number generators are re-exported from "System.Random".+module FRP.BearRiver.Random+ (+ -- * Random number generators+ RandomGen(..)+ , Random(..)++ -- * Noise, random signals, and stochastic event sources+ , noise+ , noiseR+ , occasionally+ )+ where++-- External imports+import System.Random (Random (..), RandomGen (..))++-- Internal imports (dunai)+import Control.Monad.Trans.MSF.Except (dSwitch)+import Control.Monad.Trans.MSF.Reader (readerS)+import Data.MonadicStreamFunction (MSF, constM, feedback)++-- Internal imports+import FRP.BearRiver.Event (Event (..))+import FRP.BearRiver.InternalCore (DTime, SF, Time, arr)++-- * Noise (i.e. random signal generators) and stochastic processes++-- | Noise (random signal) with default range for type in question; based on+-- "randoms".+noise :: (RandomGen g, Random b, Monad m) => g -> SF m a b+noise g0 = streamToSF (randoms g0)++-- | Noise (random signal) with specified range; based on "randomRs".+noiseR :: (RandomGen g, Random b, Monad m) => (b, b) -> g -> SF m a b+noiseR range g0 = streamToSF (randomRs range g0)++-- | Turn an infinite list of elements into an SF producing those elements. The+-- SF ignores its input.+streamToSF :: Monad m => [b] -> SF m a b+streamToSF ls = feedback ls $ arr $ fAux . snd+ where+ fAux [] = error "BearRiver: streamToSF: Empty list!"+ fAux (b:bs) = (b, bs)++-- | Stochastic event source with events occurring on average once every tAvg+-- seconds. However, no more than one event results from any one sampling+-- interval in the case of relatively sparse sampling, thus avoiding an "event+-- backlog" should sampling become more frequent at some later point in time.+occasionally :: (RandomGen g, Monad m) => g -> Time -> b -> SF m a (Event b)+occasionally g tAvg x | tAvg > 0 = tf0+ | otherwise = error $ "BearRiver: occasionally: "+ ++ "Non-positive average interval."+ where+ -- Generally, if events occur with an average frequency of f, the+ -- probability of at least one event occurring in an interval of t is given+ -- by (1 - exp (-f*t)). The goal in the following is to decide whether at+ -- least one event occurred in the interval of size dt preceding the current+ -- sample point. For the first point, we can think of the preceding interval+ -- as being 0, implying no probability of an event occurring.+ tf0 = dSwitch+ (constM $ return (NoEvent, Just ()))+ (const $ feedback (randoms g :: [Time]) $ readerS $ arr occAux)++ -- occAux :: (DTime, (a, [Time])) -> (Event b, [Time])+ occAux (_, (_, [])) = error "BearRiver: occasionally: Empty list!"+ occAux (dt, (_, r:rs)) =+ (if r < p then Event x else NoEvent, rs)+ where+ p = 1 - exp (- (dt / tAvg)) -- Probability for at least one event.
+ src/FRP/BearRiver/Simulation.hs view
@@ -0,0 +1,292 @@+{-# LANGUAGE ScopedTypeVariables #-}+-- |+-- Module : FRP.BearRiver.Simulation+-- Copyright : (c) Ivan Perez, 2014-2024+-- (c) George Giorgidze, 2007-2012+-- (c) Henrik Nilsson, 2005-2006+-- (c) Antony Courtney and Henrik Nilsson, Yale University, 2003-2004+-- License : BSD-style (see the LICENSE file in the distribution)+--+-- Maintainer : ivan.perez@keera.co.uk+-- Stability : provisional+-- Portability : non-portable (GHC extensions)+--+-- Execution/simulation of signal functions.+--+-- SFs can be executed in two ways: by running them, feeding input samples one+-- by one, obtained from a monadic environment (presumably, @IO@), or by passing+-- an input stream and calculating an output stream. The former is called+-- /reactimation/, and the latter is called /embedding/.+--+-- * Running:+-- Normally, to run an SF, you would use 'reactimate', providing input samples,+-- and consuming the output samples in the 'IO' monad. This function takes over+-- the program, implementing a "main loop". If you want more control over the+-- evaluation loop (for instance, if you are using BearRiver in combination+-- with a backend that also implements some main loop), you may want to use the+-- lower-level API for reactimation ('ReactHandle', 'reactInit', 'react').+--+-- * Embedding:+-- You can use 'embed' for testing, to evaluate SFs in a terminal, and to embed+-- an SF inside a larger system. The helper functions 'deltaEncode' and+-- 'deltaEncodeBy' facilitate producing input /signals/ from plain lists of+-- input samples.+--+-- This module also includes debugging aids needed to execute signal functions+-- step by step, which are used by BearRiver's testing facilities.+module FRP.BearRiver.Simulation+ (+ -- * Reactimation+ reactimate++ -- ** Low-level reactimation interface+ , ReactHandle+ , reactInit+ , react++ -- * Embedding+ , embed+ , embedSynch+ , deltaEncode+ , deltaEncodeBy++ -- * Debugging / Step by step simulation++ , FutureSF+ , evalAtZero+ , evalAt+ , evalFuture+ )+ where++-- External imports+import Control.Arrow (arr, (***), (>>>))+import Control.Monad (unless)+import Control.Monad.Fail (MonadFail)+import Control.Monad.Trans.Class (lift)+import Data.Functor.Identity (Identity, runIdentity)+import Data.IORef (IORef, newIORef, readIORef, writeIORef)+import Data.Maybe (fromMaybe)++-- Internal imports: dunai+import Control.Monad.Trans.MSF hiding (dSwitch)+import qualified Control.Monad.Trans.MSF as MSF+import Data.MonadicStreamFunction hiding (embed,+ reactimate)+import qualified Data.MonadicStreamFunction as MSF+import Data.MonadicStreamFunction.InternalCore (MSF (MSF, unMSF))++-- Internal imports+import FRP.BearRiver.InternalCore (DTime, SF (..))++-- * Reactimation++-- | Convenience function to run a signal function indefinitely, using a IO+-- actions to obtain new input and process the output.+--+-- This function first runs the initialization action, which provides the+-- initial input for the signal transformer at time 0.+--+-- Afterwards, an input sensing action is used to obtain new input (if any) and+-- the time since the last iteration. The argument to the input sensing function+-- indicates if it can block. If no new input is received, it is assumed to be+-- the same as in the last iteration.+--+-- After applying the signal function to the input, the actuation IO action is+-- executed. The first argument indicates if the output has changed, the second+-- gives the actual output). Actuation functions may choose to ignore the first+-- argument altogether. This action should return True if the reactimation must+-- stop, and False if it should continue.+--+-- Note that this becomes the program's /main loop/, which makes using this+-- function incompatible with GLUT, Gtk and other graphics libraries. It may+-- also impose a sizeable constraint in larger projects in which different+-- subparts run at different time steps. If you need to control the main loop+-- yourself for these or other reasons, use 'reactInit' and 'react'.+reactimate :: Monad m+ => m a -- ^ Initialization action+ -> (Bool -> m (DTime, Maybe a)) -- ^ Input sensing action+ -> (Bool -> b -> m Bool) -- ^ Actuation (output processing)+ -- action+ -> SF m a b -- ^ Signal function+ -> m ()+reactimate senseI sense actuate sf = do+ MSF.reactimateB $ senseSF >>> sfIO >>> actuateSF+ return ()+ where+ sfIO = runReaderS sf++ -- Sense+ senseSF = MSF.dSwitch senseFirst senseRest++ -- Sense: First sample+ senseFirst = constM senseI >>> arr (\x -> ((0, x), Just x))++ -- Sense: Remaining samples+ senseRest a = constM (sense True) >>> (arr id *** keepLast a)++ keepLast :: Monad m => a -> MSF m (Maybe a) a+ keepLast a = MSF $ \ma ->+ let a' = fromMaybe a ma+ in a' `seq` return (a', keepLast a')++ -- Consume/render+ actuateSF = arr (\x -> (True, x)) >>> arrM (uncurry actuate)++-- An API for animating a signal function when some other library needs to own+-- the top-level control flow:++-- reactimate's state, maintained across samples:+data ReactState a b = ReactState+ { rsActuate :: ReactHandle a b -> Bool -> b -> IO Bool+ , rsSF :: SF Identity a b+ , rsA :: a+ , rsB :: b+ }++-- | A reference to reactimate's state, maintained across samples.+newtype ReactHandle a b = ReactHandle+ { reactHandle :: IORef (ReactState a b) }++-- | Initialize a top-level reaction handle.+reactInit :: IO a -- init+ -> (ReactHandle a b -> Bool -> b -> IO Bool) -- actuate+ -> SF Identity a b+ -> IO (ReactHandle a b)+reactInit init actuate (MSF tf0) = do+ a0 <- init+ let (b0, sf) = runReader (tf0 a0) 0+ -- TODO: really need to fix this interface, since right now we just ignore+ -- termination at time 0:+ r' <- newIORef (ReactState { rsActuate = actuate, rsSF = sf+ , rsA = a0, rsB = b0+ }+ )+ let r = ReactHandle r'+ _ <- actuate r True b0+ return r++-- | Process a single input sample.+react :: ReactHandle a b+ -> (DTime, Maybe a)+ -> IO Bool+react rh (dt, ma') = do+ rs <- readIORef (reactHandle rh)+ let ReactState {rsActuate = actuate, rsSF = sf, rsA = a, rsB = _b } = rs++ let a' = fromMaybe a ma'+ (b', sf') = runReader (unMSF sf a') dt+ writeIORef (reactHandle rh) (rs {rsSF = sf', rsA = a', rsB = b'})+ done <- actuate rh True b'+ return done++-- * Embedding++-- | Given a signal function and a pair with an initial input sample for the+-- input signal, and a list of sampling times, possibly with new input samples+-- at those times, it produces a list of output samples.+--+-- This is a simplified, purely-functional version of 'reactimate'.+embed :: Monad m => SF m a b -> (a, [(DTime, Maybe a)]) -> m [b]+embed sf0 (a0, dtas) = do+ (b0, sf) <- runReaderT (unMSF sf0 a0) 0+ bs <- loop a0 sf dtas+ return $ b0 : bs+ where+ loop _ _ [] = return []+ loop aPrev sf ((dt, ma) : dtas) = do+ let a = fromMaybe aPrev ma+ (b, sf') <- runReaderT (unMSF sf a) dt+ bs <- loop a sf' dtas+ return $ a `seq` b `seq` (b : bs)++-- | Synchronous embedding. The embedded signal function is run on the supplied+-- input and time stream at a given (but variable) ratio >= 0 to the outer time+-- flow. When the ratio is 0, the embedded signal function is paused.+embedSynch :: forall m a b+ . (Monad m, MonadFail m)+ => SF m a b -> (a, [(DTime, Maybe a)]) -> SF m Double b+embedSynch sf0 (a0, dtas) = MSF tf0+ where+ tts = scanl (\t (dt, _) -> t + dt) 0 dtas++ tf0 :: Double -> ReaderT DTime m (b, SF m Double b)+ tf0 _ = do+ bbs@(b:_) <- lift (embed sf0 (a0, dtas))+ return (b, esAux 0 (zip tts bbs))++ esAux :: Double -> [(DTime, b)] -> SF m Double b+ esAux _ [] = error "BearRiver: embedSynch: Empty list!"+ -- Invarying below since esAux [] is an error.+ esAux tpPrev tbtbs = MSF tf -- True+ where+ tf r | r < 0 = error "BearRiver: embedSynch: Negative ratio."+ | otherwise = do+ dt <- ask+ let tp = tpPrev + dt * r+ (b, tbtbs') = advance tp tbtbs+ return (b, esAux tp tbtbs')++ -- Advance the time stamped stream to the perceived time tp. Under the+ -- assumption that the perceived time never goes backwards (non-negative+ -- ratio), advance maintains the invariant that the perceived time is always+ -- >= the first time stamp.+ advance _ tbtbs@[(_, b)] = (b, tbtbs)+ advance tp tbtbtbs@((_, b) : tbtbs@((t', _) : _))+ | tp < t' = (b, tbtbtbs)+ | t' <= tp = advance tp tbtbs+ advance _ _ = undefined++-- | Spaces a list of samples by a fixed time delta, avoiding unnecessary+-- samples when the input has not changed since the last sample.+deltaEncode :: Eq a => DTime -> [a] -> (a, [(DTime, Maybe a)])+deltaEncode _ [] = error "BearRiver: deltaEncode: Empty input list."+deltaEncode dt aas@(_:_) = deltaEncodeBy (==) dt aas++-- | 'deltaEncode' parameterized by the equality test.+deltaEncodeBy :: (a -> a -> Bool) -> DTime -> [a] -> (a, [(DTime, Maybe a)])+deltaEncodeBy _ _ [] = error "BearRiver: deltaEncodeBy: Empty input list."+deltaEncodeBy eq dt (a0:as) = (a0, zip (repeat dt) (debAux a0 as))+ where+ debAux _ [] = []+ debAux aPrev (a:as) | a `eq` aPrev = Nothing : debAux a as+ | otherwise = Just a : debAux a as++-- * Debugging / Step by step simulation++-- | A wrapper around an initialized SF (continuation), needed for testing and+-- debugging purposes.+newtype FutureSF m a b = FutureSF { unsafeSF :: SF m a b }++-- * Debugging / Step by step simulation++-- | Evaluate an SF, and return an output and an initialized SF.+--+-- /WARN/: Do not use this function for standard simulation. This function is+-- intended only for debugging/testing. Apart from being potentially slower and+-- consuming more memory, it also breaks the FRP abstraction by making samples+-- discrete and step based.+evalAtZero :: SF Identity a b -> a -> (b, SF Identity a b)+evalAtZero sf a = runIdentity $ runReaderT (unMSF sf a) 0++-- | Evaluate an initialized SF, and return an output and a continuation.+--+-- /WARN/: Do not use this function for standard simulation. This function is+-- intended only for debugging/testing. Apart from being potentially slower and+-- consuming more memory, it also breaks the FRP abstraction by making samples+-- discrete and step based.+evalAt :: SF Identity a b -> DTime -> a -> (b, SF Identity a b)+evalAt sf dt a = runIdentity $ runReaderT (unMSF sf a) dt++-- | Given a signal function and time delta, it moves the signal function into+-- the future, returning a new uninitialized SF and the initial output.+--+-- While the input sample refers to the present, the time delta refers to the+-- future (or to the time between the current sample and the next sample).+--+-- /WARN/: Do not use this function for standard simulation. This function is+-- intended only for debugging/testing. Apart from being potentially slower and+-- consuming more memory, it also breaks the FRP abstraction by making samples+-- discrete and step based.+evalFuture :: SF Identity a b -> a -> DTime -> (b, SF Identity a b)+evalFuture sf = flip (evalAt sf)
+ src/FRP/BearRiver/Task.hs view
@@ -0,0 +1,187 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE Rank2Types #-}+-- |+-- Module : FRP.BearRiver.Task+-- Copyright : (c) Ivan Perez, 2014-2024+-- (c) George Giorgidze, 2007-2012+-- (c) Henrik Nilsson, 2005-2006+-- (c) Antony Courtney and Henrik Nilsson, Yale University, 2003-2004+-- License : BSD3+--+-- Maintainer : ivan.perez@keera.co.uk+-- Stability : provisional+-- Portability : non-portable (GHC extensions)+--+-- Task abstraction on top of signal transformers.+module FRP.BearRiver.Task+ (+ -- * The Task type+ Task+ , mkTask+ , runTask+ , runTask_+ , taskToSF++ -- * Basic tasks+ , constT+ , sleepT+ , snapT++ -- * Basic tasks combinators+ , timeOut+ , abortWhen+ )+ where++-- External imports+#if __GLASGOW_HASKELL__ < 710+import Control.Applicative (Applicative(..))+#endif++-- Internal imports+import FRP.BearRiver.Basic (constant)+import FRP.BearRiver.Event (Event, lMerge)+import FRP.BearRiver.EventS (after, edgeBy, never, snap)+import FRP.BearRiver.InternalCore (SF, Time, arr, first, (&&&), (>>>))+import FRP.BearRiver.Switches (switch)++infixl 0 `timeOut`, `abortWhen`++-- * The Task type++-- | A task is a partially SF that may terminate with a result.+newtype Task m a b c =+ -- CPS-based representation allowing termination to be detected. Note the+ -- rank 2 polymorphic type! The representation can be changed if necessary,+ -- but the Monad laws follow trivially in this case.+ Task (forall d . (c -> SF m a (Either b d)) -> SF m a (Either b d))++unTask :: Monad m+ => Task m a b c -> ((c -> SF m a (Either b d)) -> SF m a (Either b d))+unTask (Task f) = f++-- | Creates a 'Task' from an SF that returns, as a second output, an 'Event'+-- when the SF terminates. See 'switch'.+mkTask :: Monad m => SF m a (b, Event c) -> Task m a b c+mkTask st = Task (switch (st >>> first (arr Left)))++-- | Runs a task.+--+-- The output from the resulting signal transformer is tagged with Left while+-- the underlying task is running. Once the task has terminated, the output+-- goes constant with the value Right x, where x is the value of the+-- terminating event.++-- Check name.+runTask :: Monad m => Task m a b c -> SF m a (Either b c)+runTask tk = (unTask tk) (constant . Right)++-- | Runs a task that never terminates.+--+-- The output becomes undefined once the underlying task has terminated.+--+-- Convenience function for tasks which are known not to terminate.+runTask_ :: Monad m => Task m a b c -> SF m a b+runTask_ tk =+ runTask tk+ >>> arr (either id (error "BearRiverTask: runTask_: Task terminated!"))++-- | Creates an SF that represents an SF and produces an event when the task+-- terminates, and otherwise produces just an output.+taskToSF :: Monad m => Task m a b c -> SF m a (b, Event c)+taskToSF tk =+ runTask tk+ >>> (arr (either id (error "BearRiverTask: runTask_: Task terminated!"))+ &&& edgeBy isEdge (Left undefined))+ where+ isEdge (Left _) (Right c) = Just c+ isEdge _ _ = Nothing++-- * Functor, Applicative and Monad instance++instance Monad m => Functor (Task m a b) where+ fmap f tk = Task (\k -> unTask tk (k . f))++instance Monad m => Applicative (Task m a b) where+ pure x = Task (\k -> k x)+ f <*> v = Task (\k -> (unTask f) (\c -> unTask v (k . c)))++instance Monad m => Monad (Task m a b) where+ tk >>= f = Task (\k -> unTask tk (\c -> unTask (f c) k))+ return = pure++-- Let's check the monad laws:+--+-- t >>= return+-- = \k -> t (\c -> return c k)+-- = \k -> t (\c -> (\x -> \k -> k x) c k)+-- = \k -> t (\c -> (\x -> \k' -> k' x) c k)+-- = \k -> t (\c -> k c)+-- = \k -> t k+-- = t+-- QED+--+-- return x >>= f+-- = \k -> (return x) (\c -> f c k)+-- = \k -> (\k -> k x) (\c -> f c k)+-- = \k -> (\k' -> k' x) (\c -> f c k)+-- = \k -> (\c -> f c k) x+-- = \k -> f x k+-- = f x+-- QED+--+-- (t >>= f) >>= g+-- = \k -> (t >>= f) (\c -> g c k)+-- = \k -> (\k' -> t (\c' -> f c' k')) (\c -> g c k)+-- = \k -> t (\c' -> f c' (\c -> g c k))+-- = \k -> t (\c' -> (\x -> \k' -> f x (\c -> g c k')) c' k)+-- = \k -> t (\c' -> (\x -> f x >>= g) c' k)+-- = t >>= (\x -> f x >>= g)+-- QED+--+-- No surprises (obviously, since this is essentially just the CPS monad).++-- * Basic tasks++-- | Non-terminating task with constant output b.+constT :: Monad m => b -> Task m a b c+constT b = mkTask (constant b &&& never)++-- | "Sleeps" for t seconds with constant output b.+sleepT :: Monad m => Time -> b -> Task m a b ()+sleepT t b = mkTask (constant b &&& after t ())++-- | Takes a "snapshot" of the input and terminates immediately with the input+-- value as the result.+--+-- No time passes; therefore, the following must hold:+--+-- @snapT >> snapT = snapT@+snapT :: Monad m => Task m a b a+snapT = mkTask (constant (error "BearRiverTask: snapT: Bad switch?") &&& snap)++-- * Basic tasks combinators++-- | Impose a time out on a task.+timeOut :: Monad m => Task m a b c -> Time -> Task m a b (Maybe c)+tk `timeOut` t = mkTask ((taskToSF tk &&& after t ()) >>> arr aux)+ where+ aux ((b, ec), et) = (b, lMerge (fmap Just ec) (fmap (const Nothing) et))++-- | Run a "guarding" event source (SF m a (Event b)) in parallel with a+-- (possibly non-terminating) task.+--+-- The task will be aborted at the first occurrence of the event source (if it+-- has not terminated itself before that).+--+-- Useful for separating sequencing and termination concerns. E.g. we can do+-- something "useful", but in parallel watch for a (exceptional) condition+-- which should terminate that activity, without having to check for that+-- condition explicitly during each and every phase of the activity.+--+-- Example: @tsk `abortWhen` lbp@+abortWhen :: Monad m+ => Task m a b c -> SF m a (Event d) -> Task m a b (Either c d)+tk `abortWhen` est = mkTask ((taskToSF tk &&& est) >>> arr aux)+ where+ aux ((b, ec), ed) = (b, lMerge (fmap Left ec) (fmap Right ed))
src/FRP/Yampa.hs view
@@ -9,7 +9,7 @@ import Data.Functor.Identity (Identity) -- Internal imports-import FRP.BearRiver as X hiding (SF)+import FRP.BearRiver as X hiding (FutureSF, SF) import qualified FRP.BearRiver as BR -- | Signal function (conceptually, a function between signals that respects