Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Tree: 3c5d221883
Fetching contributors…

Cannot retrieve contributors at this time

1479 lines (1206 sloc) 50.927 kB
-----------------------------------------------------------------------------
-- |
-- Module : AFRP
-- Copyright : (c) Yale University, 2003
-- License : BSD-style (see the file LICENSE)
--
-- Maintainer : antony@apocalypse.org
-- Stability : provisional
-- Portability : non-portable (uses GHC extensions)
--
-- The AFRP core.
--
-- ToDo:
-- * Check embedSynch for space leaks. It might be a good idea to force
-- "dropped frames".
-- * The internal "streamToSignal" is interesting, and a version somehow
-- accepting a time stamped stream/assuming equidistant samples, possibly
-- with an interpolation function, might be even more interesting. Perhaps
-- consider a version that applies "cycle" to the supplied list? Note that
-- there is a relation to "embedSynch" since a partial application of
-- "embedSynch" to "identity" would yield something similar. Or it is
-- in some sense the inverse of "embed".
-- * It seems the use of VectorSpace-based integrals causes more ambiguity
-- problems than before. Investigate (comments in AFRPTest.hs).
-- * Maybe "now", "after", "repeatedly" should return ().
-- There could be a bunch of utilities "nowTag", "afterTag", "repeatedlyTag",
-- and "edgeTag". Decide based on API consistency. E.g. edge already
-- returns ().
-- * Reconsider the semantics of "edgeBy". Does not disallow an edge
-- condition that persists between consecutive samples. OTOH, consider
-- a signal that alternates between two discrete values (True, False, say).
-- Surely we could then see edges on every sample. It's not really for us
-- to say whether the edge detecting function does a good job or not?
-- * We should probably introduce a type synonym Frequency here.
-- It might be most natural to give some parameters in terms of frequency
-- (like for "repeatedly" and "occasionally"). On the other hand, there
-- is "after", and it would be good if "after" and "repeatedly" are
-- mutually consitent, if "repeatedly" and "occsaionally" are consitent,
-- and if the user knows that "Time" is the only dimension he or she needs
-- to worry about.
-- * Here's an argument for why "now", "after", etc. should return "()".
-- The event value has to be a static entity anyway in these cases. So,
-- if we need them to something DYNAMIC, then the extra argument is useless.
-- Or if we don't care. If it is decided to change the interface in that
-- way, I guess we could also change Time to Frequency where that makes
-- sense. On the other hand, what's the point of "now" always returning
-- "()"? Would one not usually want to say what to return? If yes, then
-- There is something to be said for making "after" consitent with "now".
-- After all, we should have "now = after 0".
-- * Maybe "reactimate" should be parameterized on the monad type?
-- * Revisit the "reactimate" interfaces along with embedding.
-- * Revisit integration and differentiation. Valery suggests:
--
-- integral :: VectorSpace a s => SF a a
-- integral = (\ a _ dt v -> v ^+^ realToFrac dt *^ a) `iterFrom`
-- zeroVector
--
-- -- non-delayed integration (using the function's value at the current
-- -- time)
-- ndIntegral :: VectorSpace a s => SF a a
-- ndIntegral = (\ _ a' dt v -> v ^+^ realToFrac dt *^ a') `iterFrom`
-- zeroVector
--
-- derivative :: VectorSpace a s => SF a a
-- derivative = (\ a a' dt _ -> (a' ^-^ a) ^/ realToFrac dt) `iterFrom`
-- zeroVector
--
-- iterFrom :: (a -> a -> DTime -> b -> b) -> b -> SF a b
-- f `iterFrom` b = SF (iterAux b) where
-- iterAux b a = (SFTIVar (\ dt a' -> iterAux (f a a' dt b) a'), b)
-- See also the original e-mail discussion.
module AFRP (
-- Re-exported module, classes, and types
module Control.Arrow,
-- Reverse function composition and arrow plumbing aids
( # ), -- :: (a -> b) -> (b -> c) -> (a -> c), infixl 9
dup, -- :: a -> (a,a)
swap, -- :: (a,b) -> (b,a)
-- Main types
Time, -- [s] Both for time w.r.t. some reference and intervals.
SF, -- Signal Function.
Event(..), -- Events; conceptually similar to Maybe (but abstract).
-- Main instances
-- SF is an instance of Arrow and ArrowLoop. Method instances:
-- arr :: (a -> b) -> SF a b
-- (>>>) :: SF a b -> SF b c -> SF a c
-- (<<<) :: SF b c -> SF a b -> SF a c
-- first :: SF a b -> SF (a,c) (b,c)
-- second :: SF a b -> SF (c,a) (c,b)
-- (***) :: SF a b -> SF a' b' -> SF (a,a') (b,b')
-- (&&&) :: SF a b -> SF a b' -> SF a (b,b')
-- returnA :: SF a a
-- loop :: SF (a,c) (b,c) -> SF a b
-- Event is an instance of Functor, Eq, and Ord. Some method instances:
-- fmap :: (a -> b) -> Event a -> Event b
-- (==) :: Event a -> Event a -> Bool
-- (<=) :: Event a -> Event a -> Bool
-- Basic signal functions
identity, -- :: SF a a
constant, -- :: b -> SF a b
localTime, -- :: SF a Time
time, -- :: SF a Time, Other name for localTime.
-- Initialization
(-->), -- :: b -> SF a b -> SF a b, infixr 0
(>--), -- :: a -> SF a b -> SF a b, infixr 0
(-=>), -- :: (b -> b) -> SF a b -> SF a b infixr 0
(>=-), -- :: (a -> a) -> SF a b -> SF a b infixr 0
initially, -- :: a -> SF a a
-- Basic event sources
never, -- :: SF a (Event b)
now, -- :: b -> SF a (Event b)
after, -- :: Time -> b -> SF a (Event b)
repeatedly, -- :: Time -> b -> SF a (Event b)
afterEach, -- :: [(Time,b)] -> SF a (Event b)
edge, -- :: SF Bool (Event ())
iEdge, -- :: Bool -> SF Bool (Event ())
edgeTag, -- :: a -> SF Bool (Event a)
edgeJust, -- :: SF (Maybe a) (Event a)
edgeBy, -- :: (a -> a -> Maybe b) -> a -> SF a (Event b)
-- Stateful event suppression
notYet, -- :: SF (Event a) (Event a)
once, -- :: SF (Event a) (Event a)
takeEvents, -- :: Int -> SF (Event a) (Event a)
dropEvents, -- :: Int -> SF (Event a) (Event a)
-- Basic switchers
switch, dSwitch, -- :: SF a (b, Event c) -> (c -> SF a b) -> SF a b
rSwitch, drSwitch, -- :: SF a b -> SF (a,Event (SF a b)) b
kSwitch, dkSwitch, -- :: SF a b
-- -> SF (a,b) (Event c)
-- -> (SF a b -> c -> SF a b)
-- -> SF a b
-- Parallel composition and switching over collections with broadcasting
parB, -- :: Functor col => col (SF a b) -> SF a (col b)
pSwitchB,dpSwitchB, -- :: Functor col =>
-- col (SF a b)
-- -> SF (a, col b) (Event c)
-- -> (col (SF a b) -> c -> SF a (col b))
-- -> SF a (col b)
rpSwitchB,drpSwitchB,-- :: Functor col =>
-- col (SF a b)
-- -> SF (a, Event (col (SF a b)->col (SF a b)))
-- (col b)
-- Parallel composition and switching over collections with general routing
par, -- Functor col =>
-- (forall sf . (a -> col sf -> col (b, sf)))
-- -> col (SF b c)
-- -> SF a (col c)
pSwitch, dpSwitch, -- pSwitch :: Functor col =>
-- (forall sf . (a -> col sf -> col (b, sf)))
-- -> col (SF b c)
-- -> SF (a, col c) (Event d)
-- -> (col (SF b c) -> d -> SF a (col c))
-- -> SF a (col c)
rpSwitch,drpSwitch, -- Functor col =>
-- (forall sf . (a -> col sf -> col (b, sf)))
-- -> col (SF b c)
-- -> SF (a, Event (col (SF b c) -> col (SF b c)))
-- (col c)
-- Wave-form generation
hold, -- :: a -> SF (Event a) a
trackAndHold, -- :: a -> SF (Maybe a) a
-- Accumulators
accum, -- :: a -> SF (Event (a -> a)) (Event a)
accumBy, -- :: (b -> a -> b) -> b -> SF (Event a) (Event b)
accumFilter, -- :: (c -> a -> (c, Maybe b)) -> c
-- -> SF (Event a) (Event b)
-- Delays
pre, -- :: SF a a
iPre, -- :: a -> SF a a
-- Integration and differentiation
integral, -- :: VectorSpace a s => SF a a
derivative, -- :: VectorSpace a s => SF a a -- Crude!
imIntegral, -- :: VectorSpace a s => a -> SF a a
-- Loops with guaranteed well-defined feedback
loopPre, -- :: c -> SF (a,c) (b,c) -> SF a b
loopIntegral, -- :: VectorSpace c s => SF (a,c) (b,c) -> SF a b
-- Pointwise functions on events
noEvent, -- :: Event a
noEventFst, -- :: (Event a, b) -> (Event c, b)
noEventSnd, -- :: (a, Event b) -> (a, Event c)
event, -- :: a -> (b -> a) -> Event b -> a
fromEvent, -- :: Event a -> a
isEvent, -- :: Event a -> Bool
isNoEvent, -- :: Event a -> Bool
tag, -- :: Event a -> b -> Event b, infixl 8
attach, -- :: Event a -> b -> Event (a, b), infixl 8
lMerge, -- :: Event a -> Event a -> Event a, infixl 6
rMerge, -- :: Event a -> Event a -> Event a, infixl 6
merge, -- :: Event a -> Event a -> Event a, infixl 6
mergeBy, -- :: (a -> a -> a) -> Event a -> Event a -> Event a
mapMerge, -- :: (a -> c) -> (b -> c) -> (a -> b -> c)
-- -> Event a -> Event b -> Event c
mergeEvents, -- :: [Event a] -> Event a
catEvents, -- :: [Event a] -> Event [a]
joinE, -- :: Event a -> Event b -> Event (a,b),infixl 7
splitE, -- :: Event (a,b) -> (Event a, Event b)
filterE, -- :: (a -> Bool) -> Event a -> Event a
mapFilterE, -- :: (a -> Maybe b) -> Event a -> Event b
gate, -- :: Event a -> Bool -> Event a, infixl 8
-- Reactimation
reactimate, -- :: IO a
-- -> (Bool -> IO (DTime, Maybe a))
-- -> (Bool -> b -> IO Bool)
-- -> SF a b
-- -> IO ()
ReactHandle,
reactInit, -- IO a -- init
-- -> (ReactHandle a b -> Bool -> b -> IO Bool) -- actuate
-- -> SF a b
-- -> IO (ReactHandle a b)
-- process a single input sample:
react, -- ReactHandle a b
-- -> (DTime,Maybe a)
-- -> IO Bool
-- Embedding (tentative: will be revisited)
DTime, -- [s] Sampling interval, always > 0.
embed, -- :: SF a b -> (a, [(DTime, Maybe a)]) -> [b]
embedSynch, -- :: SF a b -> (a, [(DTime, Maybe a)]) -> SF Double b
deltaEncode, -- :: Eq a => DTime -> [a] -> (a, [(DTime, Maybe a)])
deltaEncodeBy -- :: (a -> a -> Bool) -> DTime -> [a]
-- -> (a, [(DTime, Maybe a)])
) where
import Monad (unless)
import Control.Arrow
import qualified Control.Category
import AFRPDiagnostics
import AFRPMiscellany (( # ), dup, swap)
import AFRPEvent
import Data.IORef
infixr 0 -->, >--, -=>, >=-
------------------------------------------------------------------------------
-- Basic type definitions with associated utilities
------------------------------------------------------------------------------
-- The time type is really a bit boguous, since, as time passes, the minimal
-- interval between two consecutive floating-point-represented time points
-- increases. A better approach is probably to pick a reasonable resolution
-- and represent time and time intervals by Integer (giving the number of
-- "ticks").
-- Time is used both for time intervals (duration), and time w.r.t. some
-- agreed reference point in time. Conceptually, Time = R, i.e. time can be 0
-- or even negative.
type Time = Double -- [s]
-- DTime is the time type for lengths of sample intervals. Conceptually,
-- DTime = R+ = { x in R | x > 0 }. Don't assume Time and DTime have the
-- same representation.
type DTime = Double -- [s]
-- Representation of signal function in initial state.
-- (Naming: "TF" stands for Transition Function.)
data SF a b = SF {sfTF :: a -> Transition a b}
-- Representation of signal function in running state.
-- It would have been nice to have a constructor SFId representing (arr id):
--
-- SFId {sfTF' :: DTime -> a -> Transition a b}
--
-- But it seems as if we need dependent types as soon as we try to exploit
-- that constructor (note that the type above is too general!), and a
-- work-around based on keeping around an extra function as a "proof" that we
-- can do the required coersions, yields codde which is no more efficient
-- than using SFArr in the first place.
-- (Naming: "TIVar" stands for "time-input-variable".)
data SF' a b
= SFConst {sfTF' :: DTime -> a -> Transition a b, sfCVal :: b}
| SFArr {sfTF' :: DTime -> a -> Transition a b, sfAFun :: a -> b}
| SFTIVar {sfTF' :: DTime -> a -> Transition a b}
-- A transition is a pair of the next state (in the form of a signal
-- function) and the output at the present time step.
type Transition a b = (SF' a b, b)
-- "Smart" constructors. The corresponding "raw" constructors should not
-- be used directly for construction.
sfConst :: b -> SF' a b
sfConst b = sf
where
sf = SFConst {sfTF' = \_ _ -> (sf, b), sfCVal = b}
sfNever :: SF' a (Event b)
sfNever = sfConst NoEvent
sfId :: SF' a a
sfId = sf
where
sf = SFArr {sfTF' = \_ a -> (sf, a), sfAFun = id}
sfArr :: (a -> b) -> SF' a b
sfArr f = sf
where
sf = SFArr {sfTF' = \_ a -> (sf, f a), sfAFun = f}
-- Freezes a "running" signal function, i.e., turns it into a continuation in
-- the form of a plain signal function.
freeze :: SF' a b -> DTime -> SF a b
freeze sf dt = SF {sfTF = (sfTF' sf) dt}
freezeCol :: Functor col => col (SF' a b) -> DTime -> col (SF a b)
freezeCol sfs dt = fmap (flip freeze dt) sfs
------------------------------------------------------------------------------
-- Arrow instance and implementation
------------------------------------------------------------------------------
instance Control.Category.Category SF where
id = arrPrim id
(.) = flip compPrim
instance Arrow SF where
arr = arrPrim
-- (>>>) = compPrim
first = firstPrim
second = secondPrim
(***) = parSplitPrim
(&&&) = parFanOutPrim
-- Lifting.
arrPrim :: (a -> b) -> SF a b
arrPrim f = SF {sfTF = \a -> (sfArr f, f a)}
-- Composition.
-- The definition exploits the following identities:
-- sf >>> constant c = constant c
-- constant c >>> arr f = constant (f c)
-- arr f >>> arr g = arr (g . f)
-- (It would have been nice to explit e.g. identity >>> sf = sf, but it would
-- seem that we need dependent types for that.)
compPrim :: SF a b -> SF b c -> SF a c
compPrim (SF {sfTF = tf10}) (SF {sfTF = tf20}) = SF {sfTF = tf0}
where
tf0 a0 = (cpAux sf1 sf2, c0)
where
(sf1, b0) = tf10 a0
(sf2, c0) = tf20 b0
cpAux _ sf2@(SFConst {}) = sfConst (sfCVal sf2)
cpAux sf1@(SFConst {}) sf2 = cpAuxC1 (sfCVal sf1) sf2
cpAux sf1@(SFArr {}) sf2 = cpAuxA1 (sfAFun sf1) sf2
cpAux sf1 sf2@(SFArr {}) = cpAuxA2 sf1 (sfAFun sf2)
cpAux sf1 sf2 = SFTIVar {sfTF' = tf}
where
tf dt a = (cpAux sf1' sf2', c)
where
(sf1', b) = (sfTF' sf1) dt a
(sf2', c) = (sfTF' sf2) dt b
cpAuxC1 _ (SFConst {sfCVal = c}) = sfConst c
cpAuxC1 b (SFArr {sfAFun = f2}) = sfConst (f2 b)
cpAuxC1 b (SFTIVar {sfTF' = tf2}) = SFTIVar {sfTF' = tf}
where
tf dt _ = (cpAuxC1 b sf2', c)
where
(sf2', c) = tf2 dt b
cpAuxA1 _ (SFConst {sfCVal = c}) = sfConst c
cpAuxA1 f1 (SFArr {sfAFun = f2}) = sfArr (f2 . f1)
cpAuxA1 f1 (SFTIVar {sfTF' = tf2}) = SFTIVar {sfTF' = tf}
where
tf dt a = (cpAuxA1 f1 sf2', c)
where
(sf2', c) = tf2 dt (f1 a)
cpAuxA2 (SFConst {sfCVal = b}) f2 = sfConst (f2 b)
cpAuxA2 (SFArr {sfAFun = f1}) f2 = sfArr (f2 . f1)
cpAuxA2 (SFTIVar {sfTF' = tf1}) f2 = SFTIVar {sfTF' = tf}
where
tf dt a = (cpAuxA2 sf1' f2, f2 b)
where
(sf1', b) = tf1 dt a
-- Widening.
-- The definition exploits the following identities:
-- first (constant b) = arr (\(_, c) -> (b, c))
-- (first (arr f)) = arr (\(a, c) -> (f a, c))
-- (It would have been nice to exploit first identity = identity, but it would
-- seem that we need dependent types for that.)
firstPrim :: SF a b -> SF (a,c) (b,c)
firstPrim (SF {sfTF = tf10}) = SF {sfTF = tf0}
where
tf0 ~(a0, c0) = (fpAux sf1, (b0, c0))
where
(sf1, b0) = tf10 a0
fpAux (SFConst {sfCVal = b}) = sfArr (\(~(_, c)) -> (b, c))
fpAux (SFArr {sfAFun = f}) = sfArr (\(~(a, c)) -> (f a, c))
fpAux sf1 = SFTIVar {sfTF' = tf}
where
tf dt ~(a, c) = (fpAux sf1', (b, c))
where
(sf1', b) = (sfTF' sf1) dt a
-- Mirror image of first.
secondPrim :: SF a b -> SF (c,a) (c,b)
secondPrim (SF {sfTF = tf10}) = SF {sfTF = tf0}
where
tf0 ~(c0, a0) = (spAux sf1, (c0, b0))
where
(sf1, b0) = tf10 a0
spAux (SFConst {sfCVal = b}) = sfArr (\(~(c, _)) -> (c, b))
spAux (SFArr {sfAFun = f}) = sfArr (\(~(c, a)) -> (c, f a))
spAux sf1 = SFTIVar {sfTF' = tf}
where
tf dt ~(c, a) = (spAux sf1', (c, b))
where
(sf1', b) = (sfTF' sf1) dt a
-- Parallel composition.
-- The definition exploits the following identities (which hold for SF):
-- constant b *** constant d = constant (b, d)
-- constant b *** arr f2 = arr (\(_, c) -> (b, f2 c)
-- arr f1 *** constant d = arr (\(a, _) -> (f1 a, d)
-- arr f1 *** arr f2 = arr (\(a, b) -> (f1 a, f2 b)
parSplitPrim :: SF a b -> SF c d -> SF (a,c) (b,d)
parSplitPrim (SF {sfTF = tf10}) (SF {sfTF = tf20}) = SF {sfTF = tf0}
where
tf0 ~(a0, c0) = (psAux sf1 sf2, (b0, d0))
where
(sf1, b0) = tf10 a0
(sf2, d0) = tf20 c0
psAux sf1@(SFConst {}) sf2 = psAuxC1 (sfCVal sf1) sf2
psAux sf1 sf2@(SFConst {}) = psAuxC2 sf1 (sfCVal sf2)
psAux sf1@(SFArr {}) sf2 = psAuxA1 (sfAFun sf1) sf2
psAux sf1 sf2@(SFArr {}) = psAuxA2 sf1 (sfAFun sf2)
psAux sf1 sf2 = SFTIVar {sfTF' = tf}
where
tf dt ~(a, c) = (psAux sf1' sf2', (b, d))
where
(sf1', b) = (sfTF' sf1) dt a
(sf2', d) = (sfTF' sf2) dt c
psAuxC1 b (SFConst {sfCVal = d}) = sfConst (b, d)
psAuxC1 b (SFArr {sfAFun = f2}) = sfArr (\(~(_, c)) -> (b, f2 c))
psAuxC1 b (SFTIVar {sfTF' = tf2}) = SFTIVar {sfTF' = tf}
where
tf dt ~(_, c) = (psAuxC1 b sf2', (b, d))
where
(sf2', d) = tf2 dt c
psAuxC2 (SFConst {sfCVal = b}) d = sfConst (b, d)
psAuxC2 (SFArr {sfAFun = f1}) d = sfArr (\(~(a, _)) -> (f1 a, d))
psAuxC2 (SFTIVar {sfTF' = tf1}) d = SFTIVar {sfTF' = tf}
where
tf dt ~(a, _) = (psAuxC2 sf1' d, (b, d))
where
(sf1', b) = tf1 dt a
psAuxA1 f1 (SFConst {sfCVal = d}) = sfArr (\(~(a,_)) -> (f1 a, d))
psAuxA1 f1 (SFArr {sfAFun = f2}) = sfArr (\(~(a,c)) -> (f1 a, f2 c))
psAuxA1 f1 (SFTIVar {sfTF' = tf2}) = SFTIVar {sfTF' = tf}
where
tf dt ~(a, c) = (psAuxA1 f1 sf2', (f1 a, d))
where
(sf2', d) = tf2 dt c
psAuxA2 (SFConst {sfCVal = b}) f2 = sfArr (\(~(_,c)) -> (b, f2 c))
psAuxA2 (SFArr {sfAFun = f1}) f2 = sfArr (\(~(a,c)) -> (f1 a, f2 c))
psAuxA2 (SFTIVar {sfTF' = tf1}) f2 = SFTIVar {sfTF' = tf}
where
tf dt ~(a, c) = (psAuxA2 sf1' f2, (b, f2 c))
where
(sf1', b) = tf1 dt a
parFanOutPrim :: SF a b -> SF a c -> SF a (b, c)
parFanOutPrim (SF {sfTF = tf10}) (SF {sfTF = tf20}) = SF {sfTF = tf0}
where
tf0 a0 = (pfoAux sf1 sf2, (b0, c0))
where
(sf1, b0) = tf10 a0
(sf2, c0) = tf20 a0
pfoAux sf1@(SFConst {}) sf2 = pfoAuxC1 (sfCVal sf1) sf2
pfoAux sf1 sf2@(SFConst {}) = pfoAuxC2 sf1 (sfCVal sf2)
pfoAux sf1@(SFArr {}) sf2 = pfoAuxA1 (sfAFun sf1) sf2
pfoAux sf1 sf2@(SFArr {}) = pfoAuxA2 sf1 (sfAFun sf2)
pfoAux sf1 sf2 = SFTIVar {sfTF' = tf}
where
tf dt a = (pfoAux sf1' sf2', (b, c))
where
(sf1', b) = (sfTF' sf1) dt a
(sf2', c) = (sfTF' sf2) dt a
pfoAuxC1 b (SFConst {sfCVal = c}) = sfConst (b, c)
pfoAuxC1 b (SFArr {sfAFun = f2}) = sfArr (\a -> (b, f2 a))
pfoAuxC1 b (SFTIVar {sfTF' = tf2}) = SFTIVar {sfTF' = tf}
where
tf dt a = (pfoAuxC1 b sf2', (b, c))
where
(sf2', c) = tf2 dt a
pfoAuxC2 (SFConst {sfCVal = b}) c = sfConst (b, c)
pfoAuxC2 (SFArr {sfAFun = f1}) c = sfArr (\a -> (f1 a, c))
pfoAuxC2 (SFTIVar {sfTF' = tf1}) c = SFTIVar {sfTF' = tf}
where
tf dt a = (pfoAuxC2 sf1' c, (b, c))
where
(sf1', b) = tf1 dt a
pfoAuxA1 f1 (SFConst {sfCVal = c}) = sfArr (\a -> (f1 a, c))
pfoAuxA1 f1 (SFArr {sfAFun = f2}) = sfArr (\a -> (f1 a ,f2 a))
pfoAuxA1 f1 (SFTIVar {sfTF' = tf2}) = SFTIVar {sfTF' = tf}
where
tf dt a = (pfoAuxA1 f1 sf2', (f1 a, c))
where
(sf2', c) = tf2 dt a
pfoAuxA2 (SFConst {sfCVal = b}) f2 = sfArr (\a -> (b, f2 a))
pfoAuxA2 (SFArr {sfAFun = f1}) f2 = sfArr (\a -> (f1 a, f2 a))
pfoAuxA2 (SFTIVar {sfTF' = tf1}) f2 = SFTIVar {sfTF' = tf}
where
tf dt a = (pfoAuxA2 sf1' f2, (b, f2 a))
where
(sf1', b) = tf1 dt a
------------------------------------------------------------------------------
-- ArrowLoop instance and implementation
------------------------------------------------------------------------------
instance ArrowLoop SF where
loop = loopPrim
loopPrim :: SF (a,c) (b,c) -> SF a b
loopPrim (SF {sfTF = tf10}) = SF {sfTF = tf0}
where
tf0 a0 = (loopAux sf1, b0)
where
(sf1, (b0, c0)) = tf10 (a0, c0)
loopAux (SFConst {sfCVal = (b, _)}) = sfConst b
loopAux (SFArr {sfAFun = f1}) = sfArr (\a -> let (b,c) = f1 (a,c)
in b)
loopAux sf1 = SFTIVar {sfTF' = tf}
where
tf dt a = (loopAux sf1', b)
where
(sf1', (b, c)) = (sfTF' sf1) dt (a, c)
------------------------------------------------------------------------------
-- Basic signal functions
------------------------------------------------------------------------------
-- Identity: identity = arr id
identity :: SF a a
identity = SF {sfTF = \a -> (sfId, a)}
-- Identity: constant b = arr (const b)
constant :: b -> SF a b
constant b = SF {sfTF = \_ -> (sfConst b, b)}
-- Outputs the time passed since the signal function instance was started.
localTime :: SF a Time
localTime = constant 1.0 >>> integral
-- Alternative name for localTime.
time :: SF a Time
time = localTime
------------------------------------------------------------------------------
-- Initialization
------------------------------------------------------------------------------
-- Initialization operator (cf. Lustre/Lucid Synchrone).
(-->) :: b -> SF a b -> SF a b
b0 --> (SF {sfTF = tf10}) = SF {sfTF = \a0 -> (fst (tf10 a0), b0)}
-- Input initialization operator.
(>--) :: a -> SF a b -> SF a b
a0 >-- (SF {sfTF = tf10}) = SF {sfTF = \_ -> tf10 a0}
-- Transform initial output value.
(-=>) :: (b -> b) -> SF a b -> SF a b
f -=> (SF {sfTF = tf10}) =
SF {sfTF = \a0 -> let (sf1, b0) = tf10 a0 in (sf1, f b0)}
-- Transform initial input value.
(>=-) :: (a -> a) -> SF a b -> SF a b
f >=- (SF {sfTF = tf10}) = SF {sfTF = \a0 -> tf10 (f a0)}
-- Override initial value of input signal.
initially :: a -> SF a a
initially = (--> identity)
------------------------------------------------------------------------------
-- Basic event sources
------------------------------------------------------------------------------
-- Event source which never occurs.
never :: SF a (Event b)
never = SF {sfTF = \_ -> (sfNever, NoEvent)}
-- Event source with a single occurrence at time 0. The value of the event
-- is given by the function argument.
now :: b -> SF a (Event b)
now b0 = (Event b0 --> never)
-- Event source with a single occurrence at or as soon after (local) time q
-- as possible.
after :: Time -> b -> SF a (Event b)
after q x = afterEach [(q,x)]
-- Event source with repeated occurrences with interval q.
-- Note: If the interval is too short w.r.t. the sampling intervals,
-- the result will be that events occur at every sample. However, no more
-- than one event results from any sampling interval, thus avoiding an
-- "event backlog" should sampling become more frequent at some later
-- point in time.
repeatedly :: Time -> b -> SF a (Event b)
repeatedly q x | q > 0 = afterEach qxs
| otherwise = usrErr "AFRP" "repeatedly" "Non-positive period."
where
qxs = (q,x):qxs
-- Event source with consecutive occurrences at the given intervals.
-- Should more than one event be scheduled to occur in any sampling interval,
-- only the first will in fact occur to avoid an event backlog.
-- Question: Should positive periods except for the first one be required?
-- Note that periods of length 0 will always be skipped except for the first.
-- Right now, periods of length 0 is allowed on the grounds that no attempt
-- is made to forbid simultaneous events elsewhere.
afterEach :: [(Time,b)] -> SF a (Event b)
afterEach [] = never
afterEach ((q,x):qxs)
| q < 0 = usrErr "AFRP" "afterEach" "Negative period."
| otherwise = SF {sfTF = tf0}
where
tf0 _ = if q <= 0 then
(scheduleNextEvent 0.0 qxs, Event x)
else
(awaitNextEvent (-q) x qxs, NoEvent)
scheduleNextEvent t [] = sfNever
scheduleNextEvent t ((q,x):qxs)
| q < 0 = usrErr "AFRP" "afterEach" "Negative period."
| t' >= 0 = scheduleNextEvent t' qxs
| otherwise = awaitNextEvent t' x qxs
where
t' = t - q
awaitNextEvent t x qxs = SFTIVar {sfTF' = tf}
where
tf dt _ | t' >= 0 = (scheduleNextEvent t' qxs, Event x)
| otherwise = (awaitNextEvent t' x qxs, NoEvent)
where
t' = t + dt
-- A rising edge detector. Useful for things like detecting key presses.
-- Note that we initialize the loop with state set to True so that there
-- will not be an occurence at t0 in the logical time frame in which
-- this is started.
edge :: SF Bool (Event ())
edge = iEdge True
iEdge :: Bool -> SF Bool (Event ())
iEdge i = edgeBy (isBoolRaisingEdge ()) i
-- Like edge, but parameterized on the tag value.
edgeTag :: a -> SF Bool (Event a)
edgeTag a = edgeBy (isBoolRaisingEdge a) True
-- Internal utility.
isBoolRaisingEdge :: a -> Bool -> Bool -> Maybe a
isBoolRaisingEdge _ False False = Nothing
isBoolRaisingEdge a False True = Just a
isBoolRaisingEdge _ True True = Nothing
isBoolRaisingEdge _ True False = Nothing
-- Detects an edge where a maybe signal is changing from nothing to something.
edgeJust :: SF (Maybe a) (Event a)
edgeJust = edgeBy isJustEdge (Just undefined)
where
isJustEdge Nothing Nothing = Nothing
isJustEdge Nothing ma@(Just _) = ma
isJustEdge (Just _) (Just _) = Nothing
isJustEdge (Just _) Nothing = Nothing
-- Edge detector parameterized on the edge detection function and initial
-- state, i.e., the previous input sample. The first argument to the
-- edge detection function is the previous sample, the second the current one.
-- !!! Is this broken!?! Does not disallow an edge condition that persists
-- !!! between consecutive samples. See discussion in ToDo list above.
edgeBy :: (a -> a -> Maybe b) -> a -> SF a (Event b)
edgeBy isEdge a_init = SF {sfTF = tf0}
where
tf0 a0 = (ebAux a0, maybeToEvent (isEdge a_init a0))
ebAux a_prev = SFTIVar {sfTF' = tf}
where
tf dt a = (ebAux a, maybeToEvent (isEdge a_prev a))
------------------------------------------------------------------------------
-- Stateful event suppression
------------------------------------------------------------------------------
-- Suppression of initial (at local time 0) event.
notYet :: SF (Event a) (Event a)
notYet = initially NoEvent
-- Suppress all but first event.
once :: SF (Event a) (Event a)
once = takeEvents 1
-- Suppress all but first n events.
takeEvents :: Int -> SF (Event a) (Event a)
takeEvents 0 = never
takeEvents n = dSwitch (arr dup) (const (NoEvent >-- takeEvents (n - 1)))
{-
-- More complicated using "switch" that "dSwitch".
takeEvents :: Int -> SF (Event a) (Event a)
takeEvents 0 = never
takeEvents (n + 1) = switch (never &&& identity) (takeEvents' n)
where
takeEvents' 0 a = now a
takeEvents' (n + 1) a = switch (now a &&& notYet) (takeEvents' n)
-}
-- Suppress first n events.
-- Here dSwitch or switch does not really matter.
dropEvents :: Int -> SF (Event a) (Event a)
dropEvents 0 = identity
dropEvents n = dSwitch (never &&& identity)
(const (NoEvent >-- dropEvents (n - 1)))
------------------------------------------------------------------------------
-- Basic switchers
------------------------------------------------------------------------------
-- Basic switch.
switch :: SF a (b, Event c) -> (c -> SF a b) -> SF a b
switch (SF {sfTF = tf10}) k = SF {sfTF = tf0}
where
tf0 a0 =
case tf10 a0 of
(sf1, (b0, NoEvent)) -> (switchAux sf1, b0)
(_, (_, Event c0)) -> sfTF (k c0) a0
switchAux (SFConst {sfCVal = (b, NoEvent)}) = sfConst b
switchAux (SFArr {sfAFun = f1}) = switchAuxA1 f1
switchAux sf1 = SFTIVar {sfTF' = tf}
where
tf dt a =
case (sfTF' sf1) dt a of
(sf1', (b, NoEvent)) -> (switchAux sf1', b)
(_, (_, Event c)) -> sfTF (k c) a
-- Note: While switch behaves as a stateless arrow at this point, that
-- could change after a switch. Hence, SFTIVar overall.
switchAuxA1 f1 = sf
where
sf = SFTIVar {sfTF' = tf}
tf _ a =
case f1 a of
(b, NoEvent) -> (sf, b)
(_, Event c) -> sfTF (k c) a
-- Switch with delayed observation.
dSwitch :: SF a (b, Event c) -> (c -> SF a b) -> SF a b
dSwitch (SF {sfTF = tf10}) k = SF {sfTF = tf0}
where
tf0 a0 =
let (sf1, (b0, ec0)) = tf10 a0
in (case ec0 of
NoEvent -> dSwitchAux sf1
Event c0 -> fst (sfTF (k c0) a0),
b0)
dSwitchAux (SFConst {sfCVal = (b, NoEvent)}) = sfConst b
dSwitchAux (SFArr {sfAFun = f1}) = dSwitchAuxA1 f1
dSwitchAux sf1 = SFTIVar {sfTF' = tf}
where
tf dt a =
let (sf1', (b, ec)) = (sfTF' sf1) dt a
in (case ec of
NoEvent -> dSwitchAux sf1'
Event c -> fst (sfTF (k c) a),
b)
-- Note: While dSwitch behaves as a stateless arrow at this point, that
-- could change after a switch. Hence, SFTIVar overall.
dSwitchAuxA1 f1 = sf
where
sf = SFTIVar {sfTF' = tf}
tf _ a =
let (b, ec) = f1 a
in (case ec of
NoEvent -> sf
Event c -> fst (sfTF (k c) a),
b)
-- Recurring switch.
rSwitch :: SF a b -> SF (a, Event (SF a b)) b
rSwitch sf = switch (first sf) ((noEventSnd >=-) . rSwitch)
{-
-- Old version. New is more efficient. Which one is clearer?
rSwitch :: SF a b -> SF (a, Event (SF a b)) b
rSwitch sf = switch (first sf) rSwitch'
where
rSwitch' sf = switch (sf *** notYet) rSwitch'
-}
-- Recurring switch with delayed observation.
drSwitch :: SF a b -> SF (a, Event (SF a b)) b
drSwitch sf = dSwitch (first sf) ((noEventSnd >=-) . drSwitch)
{-
-- Old version. New is more efficient. Which one is clearer?
drSwitch :: SF a b -> SF (a, Event (SF a b)) b
drSwitch sf = dSwitch (first sf) drSwitch'
where
drSwitch' sf = dSwitch (sf *** notYet) drSwitch'
-}
-- "Call-with-current-continuation" switch.
kSwitch :: SF a b -> SF (a,b) (Event c) -> (SF a b -> c -> SF a b) -> SF a b
kSwitch sf10@(SF {sfTF = tf10}) (SF {sfTF = tfe0}) k = SF {sfTF = tf0}
where
tf0 a0 =
let (sf1, b0) = tf10 a0
in
case tfe0 (a0, b0) of
(sfe, NoEvent) -> (kSwitchAux sf1 sfe, b0)
(_, Event c0) -> sfTF (k sf10 c0) a0
kSwitchAux sf1 (SFConst {sfCVal = NoEvent}) = sf1
kSwitchAux sf1 sfe = SFTIVar {sfTF' = tf}
where
tf dt a =
let (sf1', b) = (sfTF' sf1) dt a
in
case (sfTF' sfe) dt (a, b) of
(sfe', NoEvent) -> (kSwitchAux sf1' sfe', b)
(_, Event c) -> sfTF (k (freeze sf1 dt) c) a
-- kSwitch with delayed observation.
dkSwitch :: SF a b -> SF (a,b) (Event c) -> (SF a b -> c -> SF a b) -> SF a b
dkSwitch sf10@(SF {sfTF = tf10}) (SF {sfTF = tfe0}) k = SF {sfTF = tf0}
where
tf0 a0 =
let (sf1, b0) = tf10 a0
in (case tfe0 (a0, b0) of
(sfe, NoEvent) -> dkSwitchAux sf1 sfe
(_, Event c0) -> fst (sfTF (k sf10 c0) a0),
b0)
dkSwitchAux sf1 (SFConst {sfCVal = NoEvent}) = sf1
dkSwitchAux sf1 sfe = SFTIVar {sfTF' = tf}
where
tf dt a =
let (sf1', b) = (sfTF' sf1) dt a
in (case (sfTF' sfe) dt (a, b) of
(sfe', NoEvent) -> dkSwitchAux sf1' sfe'
(_, Event c) -> fst (sfTF (k (freeze sf1 dt) c) a),
b)
------------------------------------------------------------------------------
-- Parallel composition and switching over collections with broadcasting
------------------------------------------------------------------------------
broadcast :: Functor col => a -> col sf -> col (a, sf)
broadcast a sfs = fmap (\sf -> (a, sf)) sfs
-- Spatial parallel composition of a signal function collection.
parB :: Functor col => col (SF a b) -> SF a (col b)
parB = par broadcast
-- Parallel switch (dynamic collection of signal functions spatially composed
-- in parallel).
pSwitchB :: Functor col =>
col (SF a b) -> SF (a,col b) (Event c) -> (col (SF a b)->c-> SF a (col b))
-> SF a (col b)
pSwitchB = pSwitch broadcast
dpSwitchB :: Functor col =>
col (SF a b) -> SF (a,col b) (Event c) -> (col (SF a b)->c->SF a (col b))
-> SF a (col b)
dpSwitchB = dpSwitch broadcast
rpSwitchB :: Functor col =>
col (SF a b) -> SF (a, Event (col (SF a b) -> col (SF a b))) (col b)
rpSwitchB = rpSwitch broadcast
drpSwitchB :: Functor col =>
col (SF a b) -> SF (a, Event (col (SF a b) -> col (SF a b))) (col b)
drpSwitchB = drpSwitch broadcast
------------------------------------------------------------------------------
-- Parallel composition and switching over collections with general routing
------------------------------------------------------------------------------
-- Spatial parallel composition of a signal function collection parameterized
-- on the routing function.
-- rf ......... Routing function: determines the input to each signal function
-- in the collection. IMPORTANT! The routing function MUST
-- preserve the structure of the signal function collection.
-- sfs0 ....... Signal function collection.
-- Returns the spatial parallel composition of the supplied signal functions.
par :: Functor col =>
(forall sf . (a -> col sf -> col (b, sf)))
-> col (SF b c)
-> SF a (col c)
par rf sfs0 = SF {sfTF = tf0}
where
tf0 a0 =
let bsfs0 = rf a0 sfs0
sfcs0 = fmap (\(b0, sf0) -> (sfTF sf0) b0) bsfs0
sfs = fmap fst sfcs0
cs0 = fmap snd sfcs0
in
(parAux rf sfs, cs0)
-- Internal definition. Also used in parallel swithers.
parAux :: Functor col =>
(forall sf . (a -> col sf -> col (b, sf)))
-> col (SF' b c)
-> SF' a (col c)
parAux rf sfs = SFTIVar {sfTF' = tf}
where
tf dt a =
let bsfs = rf a sfs
sfcs' = fmap (\(b, sf) -> (sfTF' sf) dt b) bsfs
sfs' = fmap fst sfcs'
cs = fmap snd sfcs'
in
(parAux rf sfs', cs)
-- Parallel switch parameterized on the routing function. This is the most
-- general switch from which all other (non-delayed) switches in principle
-- can be derived. The signal function collection is spatially composed in
-- parallel and run until the event signal function has an occurrence. Once
-- the switching event occurs, all signal function are "frozen" and their
-- continuations are passed to the continuation function, along with the
-- event value.
-- rf ......... Routing function: determines the input to each signal function
-- in the collection. IMPORTANT! The routing function has an
-- obligation to preserve the structure of the signal function
-- collection.
-- sfs0 ....... Signal function collection.
-- sfe0 ....... Signal function generating the switching event.
-- k .......... Continuation to be invoked once event occurs.
-- Returns the resulting signal function.
pSwitch :: Functor col =>
(forall sf . (a -> col sf -> col (b, sf)))
-> col (SF b c)
-> SF (a, col c) (Event d)
-> (col (SF b c) -> d -> SF a (col c))
-> SF a (col c)
pSwitch rf sfs0 sfe0 k = SF {sfTF = tf0}
where
tf0 a0 =
let bsfs0 = rf a0 sfs0
sfcs0 = fmap (\(b0, sf0) -> (sfTF sf0) b0) bsfs0
sfs = fmap fst sfcs0
cs0 = fmap snd sfcs0
in
case (sfTF sfe0) (a0, cs0) of
(sfe, NoEvent) -> (pSwitchAux sfs sfe, cs0)
(_, Event d0) -> sfTF (k sfs0 d0) a0
pSwitchAux sfs (SFConst {sfCVal = NoEvent}) = parAux rf sfs
pSwitchAux sfs sfe = SFTIVar {sfTF' = tf}
where
tf dt a =
let bsfs = rf a sfs
sfcs' = fmap (\(b, sf) -> (sfTF' sf) dt b) bsfs
sfs' = fmap fst sfcs'
cs = fmap snd sfcs'
in
case (sfTF' sfe) dt (a, cs) of
(sfe', NoEvent) -> (pSwitchAux sfs' sfe', cs)
(_, Event d) -> sfTF (k (freezeCol sfs dt) d) a
-- Parallel switch with delayed observation parameterized on the routing
-- function.
dpSwitch :: Functor col =>
(forall sf . (a -> col sf -> col (b, sf)))
-> col (SF b c)
-> SF (a, col c) (Event d)
-> (col (SF b c) -> d -> SF a (col c))
-> SF a (col c)
dpSwitch rf sfs0 sfe0 k = SF {sfTF = tf0}
where
tf0 a0 =
let bsfs0 = rf a0 sfs0
sfcs0 = fmap (\(b0, sf0) -> (sfTF sf0) b0) bsfs0
cs0 = fmap snd sfcs0
in
(case (sfTF sfe0) (a0, cs0) of
(sfe, NoEvent) -> dpSwitchAux (fmap fst sfcs0) sfe
(_, Event d0) -> fst (sfTF (k sfs0 d0) a0),
cs0)
dpSwitchAux sfs (SFConst {sfCVal = NoEvent}) = parAux rf sfs
dpSwitchAux sfs sfe = SFTIVar {sfTF' = tf}
where
tf dt a =
let bsfs = rf a sfs
sfcs' = fmap (\(b, sf) -> (sfTF' sf) dt b) bsfs
cs = fmap snd sfcs'
in
(case (sfTF' sfe) dt (a, cs) of
(sfe', NoEvent) -> dpSwitchAux (fmap fst sfcs')
sfe'
(_, Event d) -> fst (sfTF (k (freezeCol sfs dt)
d)
a),
cs)
-- Recurring parallel switch parameterized on the routing function.
-- rf ......... Routing function: determines the input to each signal function
-- in the collection. IMPORTANT! The routing function has an
-- obligation to preserve the structure of the signal function
-- collection.
-- sfs ........ Initial signal function collection.
-- Returns the resulting signal function.
rpSwitch :: Functor col =>
(forall sf . (a -> col sf -> col (b, sf)))
-> col (SF b c) -> SF (a, Event (col (SF b c) -> col (SF b c))) (col c)
rpSwitch rf sfs =
pSwitch (rf . fst) sfs (arr (snd . fst)) $ \sfs' f ->
noEventSnd >=- rpSwitch rf (f sfs')
{-
rpSwitch rf sfs = pSwitch (rf . fst) sfs (arr (snd . fst)) k
where
k sfs f = rpSwitch' (f sfs)
rpSwitch' sfs = pSwitch (rf . fst) sfs (NoEvent --> arr (snd . fst)) k
-}
-- Recurring parallel switch with delayed observation parameterized on the
-- routing function.
drpSwitch :: Functor col =>
(forall sf . (a -> col sf -> col (b, sf)))
-> col (SF b c) -> SF (a, Event (col (SF b c) -> col (SF b c))) (col c)
drpSwitch rf sfs =
dpSwitch (rf . fst) sfs (arr (snd . fst)) $ \sfs' f ->
noEventSnd >=- drpSwitch rf (f sfs')
{-
drpSwitch rf sfs = dpSwitch (rf . fst) sfs (arr (snd . fst)) k
where
k sfs f = drpSwitch' (f sfs)
drpSwitch' sfs = dpSwitch (rf . fst) sfs (NoEvent-->arr (snd . fst)) k
-}
------------------------------------------------------------------------------
-- Wave-form generation
------------------------------------------------------------------------------
-- Zero-order hold.
hold :: a -> SF (Event a) a
hold a_init = switch (constant a_init &&& identity) ((NoEvent >--) . hold)
-- Tracks input signal when available, holds last value when disappears.
trackAndHold :: a -> SF (Maybe a) a
trackAndHold a_init = arr (maybe NoEvent Event) >>> hold a_init
------------------------------------------------------------------------------
-- Accumulators
------------------------------------------------------------------------------
accum :: a -> SF (Event (a -> a)) (Event a)
accum = accumBy (flip ($))
accumBy :: (b -> a -> b) -> b -> SF (Event a) (Event b)
accumBy f b_init = switch (never &&& identity) $ \a -> abAux (f b_init a)
where
abAux b = switch (now b &&& notYet) $ \a -> abAux (f b a)
{-
-- Identity: accumBy f = accumFilter (\b a -> let b' = f b a in (b',Just b'))
accumBy :: (b -> a -> b) -> b -> SF (Event a) (Event b)
accumBy f b_init = SF {sfTF = tf0}
where
tf0 NoEvent = (abAux b_init, NoEvent)
tf0 (Event a0) = let b' = f b_init a0
in (abAux b', Event b')
abAux b = SFTIVar {sfTF' = tf}
where
tf _ NoEvent = (abAux b, NoEvent)
tf _ (Event a) = let b' = f b a
in (abAux b', Event b')
-}
{-
accumFilter :: (c -> a -> (c, Maybe b)) -> c -> SF (Event a) (Event b)
accumFilter f c_init = SF {sfTF = tf0}
where
tf0 NoEvent = (afAux c_init, NoEvent)
tf0 (Event a0) = case f c_init a0 of
(c', Nothing) -> (afAux c', NoEvent)
(c', Just b0) -> (afAux c', Event b0)
afAux c = SFTIVar {sfTF' = tf}
where
tf _ NoEvent = (afAux c, NoEvent)
tf _ (Event a) = case f c a of
(c', Nothing) -> (afAux c', NoEvent)
(c', Just b) -> (afAux c', Event b)
-}
accumFilter :: (c -> a -> (c, Maybe b)) -> c -> SF (Event a) (Event b)
accumFilter f c_init = switch (never &&& identity) $ \a -> afAux (f c_init a)
where
afAux (c, Nothing) = switch (never &&& notYet) $ \a -> afAux (f c a)
afAux (c, Just b) = switch (now b &&& notYet) $ \a -> afAux (f c a)
------------------------------------------------------------------------------
-- Delays
------------------------------------------------------------------------------
-- Uninitialized delay operator.
-- !!! The seq helps in the dynamic delay line example. But is it a good
-- !!! idea in general? Are there other accumulators which should be seq'ed
-- !!! as well? E.g. accum? Switch? Anywhere else? What's the underlying
-- !!! design principle? What can the user assume?
pre = SF {sfTF = tf0}
where
tf0 a0 = (preAux a0, usrErr "AFRP" "pre" "Uninitialized pre operator.")
preAux a_prev = SFTIVar {sfTF' = tf}
where
tf dt a = {- a_prev `seq` -} (preAux a, a_prev)
-- Initialized delay operator.
iPre :: a -> SF a a
iPre = (--> pre)
------------------------------------------------------------------------------
-- Integraltion and differentiation
------------------------------------------------------------------------------
-- Integration using the rectangle rule.
integral :: Fractional a => SF a a
integral = SF {sfTF = tf0}
where
igrl0 = 0
tf0 a0 = (integralAux igrl0 a0, igrl0)
integralAux igrl a_prev = SFTIVar {sfTF' = tf}
where
tf dt a = (integralAux igrl' a, igrl')
where
igrl' = igrl + realToFrac dt * a_prev
-- "immediate" integration (using the function's value at the current time)
imIntegral :: Fractional a => a -> SF a a
imIntegral = ((\ _ a' dt v -> v + realToFrac dt * a') `iterFrom`)
iterFrom :: (a -> a -> DTime -> b -> b) -> b -> SF a b
f `iterFrom` b = SF (iterAux b) where
iterAux b a = (SFTIVar (\ dt a' -> iterAux (f a a' dt b) a'), b)
{-
integral :: Fractional a => SF a a
integral = SF {sfTF = tf0}
where
igrl0 = 0.0
tf0 a0 = (integralAux igrl0 a0, igrl0)
integralAux igrl a_prev = SFTIVar {sfTF' = tf}
where
tf dt a = (integralAux igrl' a, igrl')
where
igrl' = igrl + a_prev * realToFrac dt
-}
-- This is extremely crude. Use at your own risk.
derivative :: Fractional a => SF a a
derivative = SF {sfTF = tf0}
where
tf0 a0 = (derivativeAux a0, 0)
derivativeAux a_prev = SFTIVar {sfTF' = tf}
where
tf dt a = (derivativeAux a, (a - a_prev) / realToFrac dt)
------------------------------------------------------------------------------
-- Loops with guaranteed well-defined feedback
------------------------------------------------------------------------------
loopPre :: c -> SF (a,c) (b,c) -> SF a b
loopPre c_init sf = loop (second (iPre c_init) >>> sf)
loopIntegral :: Fractional c => SF (a,c) (b,c) -> SF a b
loopIntegral sf = loop (second integral >>> sf)
------------------------------------------------------------------------------
-- Reactimation
------------------------------------------------------------------------------
-- Reactimation of a signal function.
-- init ....... IO action for initialization. Will only be invoked once,
-- at (logical) time 0, before first call to "sense".
-- Expected to return the value of input at time 0.
-- sense ...... IO action for sensing of system input.
-- arg. #1 ....... True: action may block, waiting for an OS event.
-- False: action must not block.
-- res. #1 ....... Time interval since previous invocation of the sensing
-- action (or, the first time round, the init action),
-- returned. The interval must be _strictly_ greater
-- than 0. Thus even a non-blocking invocation must
-- ensure that time progresses.
-- res. #2 ....... Nothing: input is unchanged w.r.t. the previously
-- returned input sample.
-- Just i: the input is currently i.
-- It is OK to always return "Just", even if input is
-- unchanged.
-- actuate .... IO action for outputting the system output.
-- arg. #1 ....... True: output may have changed from previous output
-- sample.
-- False: output is definitely unchanged from previous
-- output sample.
-- It is OK to ignore argument #1 and assume that the
-- the output has always changed.
-- arg. #2 ....... Current output sample.
-- result ....... Termination flag. Once True, reactimate will exit
-- the reactimation loop and return to its caller.
-- sf ......... Signal function to reactimate.
reactimate :: IO a
-> (Bool -> IO (DTime, Maybe a))
-> (Bool -> b -> IO Bool)
-> SF a b
-> IO ()
reactimate init sense actuate (SF {sfTF = tf0}) =
do
a0 <- init
let (sf, b0) = tf0 a0
loop sf a0 b0
where
loop sf a b = do
done <- actuate True b
unless (a `seq` b `seq` done) $ do
(dt, ma_prime) <- sense False
let a_prime = maybe a id ma_prime
(sf_prime, b_prime) = (sfTF' sf) dt a_prime
loop sf_prime a_prime b_prime
-- 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' a b,
rsA :: a,
rsB :: b
}
type ReactHandle a b = IORef (ReactState a b)
-- initialize top-level reaction handle
reactInit :: IO a -- init
-> (ReactHandle a b -> Bool -> b -> IO Bool) -- actuate
-> SF a b
-> IO (ReactHandle a b)
reactInit init actuate (SF {sfTF = tf0}) =
do a0 <- init
let (sf,b0) = tf0 a0
-- 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 })
done <- 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@(ReactState {rsActuate = actuate,
rsSF = sf,
rsA = a,
rsB = b }) <- readIORef rh
let a' = maybe a id ma'
(sf',b') = (sfTF' sf) dt a'
writeIORef rh (rs {rsSF = sf',rsA = a',rsB = b'})
done <- actuate rh True b'
return done
------------------------------------------------------------------------------
-- Embedding
------------------------------------------------------------------------------
-- New embed interface. We will probably have to revisit this. To run an
-- embedded signal function while retaining full control (e.g. start and
-- stop at will), one would probably need a continuation based interface
-- (as well as a continuation based underlying implementation).
--
-- E.g. here are interesting alternative (or maybe complementary)
-- signatures:
--
-- sample :: SF a b -> SF (Event a) (Event b)
-- sample' :: SF a b -> SF (Event (DTime, a)) (Event b)
embed :: SF a b -> (a, [(DTime, Maybe a)]) -> [b]
embed sf0 (a0, dtas) = b0 : loop a0 sf dtas
where
(sf, b0) = (sfTF sf0) a0
loop a_prev sf [] = []
loop a_prev sf ((dt, ma) : dtas) =
b : (a `seq` b `seq` (loop a sf' dtas))
where
a = maybe a_prev id ma
(sf', b) = (sfTF' sf) dt a
-- 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.
-- !!! Should "dropped frames" be forced to avoid space leaks?
-- !!! It's kind of hard to se why, but "frame dropping" was a problem
-- !!! in the old robot simulator. Try to find an example!
embedSynch :: SF a b -> (a, [(DTime, Maybe a)]) -> SF Double b
embedSynch sf0 (a0, dtas) = SF {sfTF = tf0}
where
tts = scanl (\t (dt, _) -> t + dt) 0 dtas
bbs@(b:_) = embed sf0 (a0, dtas)
tf0 r = (esAux 0 (zip tts bbs), b)
esAux _ [] = intErr "AFRP" "embedSynch" "Empty list!"
esAux tp_prev tbtbs = SFTIVar {sfTF' = tf}
where
tf dt r | r < 0 = usrErr "AFRP" "embedSynch"
"Negative ratio."
| otherwise = let tp = tp_prev + dt * r
(b, tbtbs') = advance tp tbtbs
in
(esAux tp tbtbs', b)
-- 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 tp tbtbs@[(t, b)] = (b, tbtbs)
advance tp tbtbtbs@((_, b) : tbtbs@((t', _) : _))
| tp < t' = (b, tbtbtbs)
| t' <= tp = advance tp tbtbs
deltaEncode :: Eq a => DTime -> [a] -> (a, [(DTime, Maybe a)])
deltaEncode _ [] = usrErr "AFRP" "deltaEncode" "Empty input list."
deltaEncode dt aas@(_:_) = deltaEncodeBy (==) dt aas
deltaEncodeBy :: (a -> a -> Bool) -> DTime -> [a] -> (a, [(DTime, Maybe a)])
deltaEncodeBy _ _ [] = usrErr "AFRP" "deltaEncodeBy" "Empty input list."
deltaEncodeBy eq dt (a0:as) = (a0, zip (repeat dt) (debAux a0 as))
where
debAux a_prev [] = []
debAux a_prev (a:as) | a `eq` a_prev = Nothing : debAux a as
| otherwise = Just a : debAux a as
Jump to Line
Something went wrong with that request. Please try again.