{-# LANGUAGE GADTs, Rank2Types, CPP #-} ----------------------------------------------------------------------------------------- -- | -- Module : FRP.Yampa -- Copyright : (c) Antony Courtney and Henrik Nilsson, Yale University, 2003 -- License : BSD-style (see the LICENSE file in the distribution) -- -- Maintainer : ivan.perez@keera.co.uk -- Stability : provisional -- Portability : non-portable (GHC extensions) -- -- -- Domain-specific language embedded in Haskell for programming hybrid (mixed -- discrete-time and continuous-time) systems. Yampa is based on the concepts -- of Functional Reactive Programming (FRP) and is structured using arrow -- combinators. -- -- You can find examples, screenshots, tutorials and documentation here: -- -- <https://github.com/ivanperez-keera/Yampa> -- -- <https://github.com/ivanperez-keera/Yampa/tree/master/examples> -- -- <https://wiki.haskell.org/Yampa> -- -- -- Structuring a hybrid system in Yampa is done based on two main concepts: -- -- * Signal Functions: 'SF'. Yampa is based on the concept of Signal Functions, -- which are functions from a typed input signal to a typed output signal. -- Conceptually, signals are functions from Time to Value, where time are the -- real numbers and, computationally, a very dense approximation (Double) is -- used. -- -- * Events: 'Event'. Values that may or may not occur (and would probably -- occur rarely). It is often used for incoming network messages, mouse -- clicks, etc. Events are used as values carried by signals. -- -- A complete Yampa system is defined as one Signal Function from some -- type @a@ to a type @b@. The execution of this signal transformer -- with specific input can be accomplished by means of two functions: -- 'reactimate' (which needs an initialization action, -- an input sensing action and an actuation/consumer action and executes -- until explicitly stopped), and 'react' (which executes only one cycle). -- -- -- Main Yampa modules: -- -- * "FRP.Yampa" -- This exports all FRP-related functions -- -- * "FRP.Yampa.Task" -- -- Minimal Complete FRP Definition: -- -- * "FRP.Yampa.Core" -- -- Different FRP aspects: -- -- * "FRP.Yampa.Basic" -- -- * "FRP.Yampa.Conditional" -- -- * "FRP.Yampa.Delays" -- -- * "FRP.Yampa.Event" -- -- * "FRP.Yampa.EventS" -- Event consuming/producing SFs. To be renamed. -- -- * "FRP.Yampa.Hybrid" -- Hybrid (discrete/continuous) SFs -- -- * "FRP.Yampa.Integration" -- -- * "FRP.Yampa.Loop" -- -- * "FRP.Yampa.Random" -- -- * "FRP.Yampa.Scan" -- -- * "FRP.Yampa.Switches" -- -- * "FRP.Yampa.Time" -- -- * "FRP.Yampa.Simulation" -- Reactimation/evaluation -- -- Internals: -- -- * "FRP.Yampa.InternalCore" -- Module not exposed. -- -- Geometry: -- -- * "FRP.Yampa.Geometry" -- -- * "FRP.Yampa.AffineSpace" -- -- * "FRP.Yampa.VectorSpace" -- -- * "FRP.Yampa.Point2" -- -- * "FRP.Yampa.Point3" -- -- * "FRP.Yampa.Vector2" -- -- * "FRP.Yampa.Vector3" -- -- Old legacy code: -- -- * "FRP.Yampa.Diagnostics" -- -- * "FRP.Yampa.Forceable" -- -- * "FRP.Yampa.Internals" -- No longer in use -- -- * "FRP.Yampa.MergeableRecord" -- -- * "FRP.Yampa.Miscellany" -- -- * "FRP.Yampa.Utilities" -- -- This will be the last version of Yampa to include mergeable records, point2 -- and point3, vector2 and vector3, and other auxiliary definitions. The -- internals have now changed. Also, please let us know if you see any problems -- with the new project structure. -- ToDo: -- -- - Specialize def. of repeatedly. Could have an impact on invaders. -- -- - New defs for accs using SFAcc -- -- - Make sure opt worked: e.g. -- -- - > repeatedly >>> count >>> arr (fmap sqr) -- -- - Introduce SFAccHld. -- -- - See if possible to unify AccHld wity Acc??? They are so close. -- -- - Introduce SScan. BUT KEEP IN MIND: Most if not all opts would -- - have been possible without GADTs??? -- -- - Look into pairs. At least pairing of SScan ought to be interesting. -- -- - Would be nice if we could get rid of first & second with impunity -- - thanks to Id optimizations. That's a clear win, with or without -- - an explicit pair combinator. -- -- - delayEventCat is a bit complicated ... -- -- -- Random ideas: -- -- - What if one used rules to optimize -- - (arr :: SF a ()) to (constant ()) -- - (arr :: SF a a) to identity -- But inspection of invader source code seem to indicate that -- these are not very common cases at all. -- -- - It would be nice if it was possible to come up with opt. rules -- that are invariant of how signal function expressions are -- parenthesized. Right now, we have e.g. -- arr f >>> (constant c >>> sf) -- being optimized to -- cpAuxA1 f (cpAuxC1 c sf) -- whereas it clearly should be possible to optimize to just -- cpAuxC1 c sf -- What if we didn't use SF' but -- SFComp :: <tfun> -> SF' a b -> SF' b c -> SF' a c -- ??? -- -- - The transition function would still be optimized in (pretty much) -- the current way, but it would still be possible to look "inside" -- composed signal functions for lost optimization opts. -- Seems to me this could be done without too much extra effort/no dupl. -- work. -- E.g. new cpAux, the general case: -- -- @ -- cpAux sf1 sf2 = SFComp tf sf1 sf2 -- where -- tf dt a = (cpAux sf1' sf2', c) -- where -- (sf1', b) = (sfTF' sf1) dt a -- (sf2', c) = (sfTF' sf2) dt b -- @ -- -- - The ONLY change was changing the constructor from SF' to SFComp and -- adding sf1 and sf2 to the constructor app.! -- -- - An optimized case: -- cpAuxC1 b sf1 sf2 = SFComp tf sf1 sf2 -- So cpAuxC1 gets an extra arg, and we change the constructor. -- But how to exploit without writing 1000s of rules??? -- Maybe define predicates on SFComp to see if the first or second -- sf are "interesting", and if so, make "reassociate" and make a -- recursive call? E.g. we're in the arr case, and the first sf is another -- arr, so we'd like to combine the two. -- -- - It would also be intersting, then, to know when to STOP playing this -- game, due to the overhead involved. -- -- - Why don't we have a "SWITCH" constructor that indicates that the -- structure will change, and thus that it is worthwile to keep -- looking for opt. opportunities, whereas a plain "SF'" would -- indicate that things NEVER are going to change, and thus we can just -- as well give up? ----------------------------------------------------------------------------------------- module FRP.Yampa ( -- Re-exported module, classes, and types module Control.Arrow, module FRP.Yampa.VectorSpace, RandomGen(..), Random(..), -- * Basic definitions Time, -- [s] Both for time w.r.t. some reference and intervals. DTime, -- [s] Sampling interval, always > 0. SF, -- Signal Function. Event(..), -- Events; conceptually similar to Maybe (but abstract). -- Temporary! -- SF(..), sfTF', -- 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 -- ** Lifting arrPrim, arrEPrim, -- For optimization -- * Signal functions -- ** 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 (-:>), -- :: 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 -- ** Simple, stateful signal processing sscan, -- :: (b -> a -> b) -> b -> SF a b sscanPrim, -- :: (c -> a -> Maybe (c, b)) -> c -> b -> SF a b -- * Events -- ** 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) afterEachCat, -- :: [(Time,b)] -> SF a (Event [b]) delayEvent, -- :: Time -> SF (Event a) (Event a) delayEventCat, -- :: Time -> SF (Event a) (Event [a]) 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) maybeToEvent, -- :: Maybe a -> Event a -- ** 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) -- ** 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 tagWith, -- :: b -> Event a -> Event b, 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 -- * Switching -- ** 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 -- *** 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) -- -- * Discrete to continuous-time signal functions -- ** Wave-form generation hold, -- :: a -> SF (Event a) a dHold, -- :: a -> SF (Event a) a trackAndHold, -- :: a -> SF (Maybe a) a -- ** Accumulators accum, -- :: a -> SF (Event (a -> a)) (Event a) accumHold, -- :: a -> SF (Event (a -> a)) a dAccumHold, -- :: a -> SF (Event (a -> a)) a accumBy, -- :: (b -> a -> b) -> b -> SF (Event a) (Event b) accumHoldBy, -- :: (b -> a -> b) -> b -> SF (Event a) b dAccumHoldBy, -- :: (b -> a -> b) -> b -> SF (Event a) b accumFilter, -- :: (c -> a -> (c, Maybe b)) -> c -- -> SF (Event a) (Event b) -- * Delays -- ** Basic delays pre, -- :: SF a a iPre, -- :: a -> SF a a -- ** Timed delays delay, -- :: Time -> a -> SF a a -- ** Variable delay pause, -- :: b -> SF a b -> SF a Bool -> SF a b -- * State keeping combinators -- ** 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 -- ** Integration and differentiation integral, -- :: VectorSpace a s => SF a a imIntegral, -- :: VectorSpace a s => a -> SF a a impulseIntegral, -- :: VectorSpace a k => SF (a, Event a) a count, -- :: Integral b => SF (Event a) (Event b) derivative, -- :: VectorSpace a s => SF a a -- Crude! -- Temporarily hidden, but will eventually be made public. iterFrom, -- :: (a -> a -> DTime -> b -> b) -> b -> SF a b -- * Noise (random signal) sources and stochastic event sources noise, -- :: noise :: (RandomGen g, Random b) => -- g -> SF a b noiseR, -- :: noise :: (RandomGen g, Random b) => -- (b,b) -> g -> SF a b occasionally, -- :: RandomGen g => g -> Time -> b -> SF a (Event b) -- * Execution/simulation -- ** 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) 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)]) -- * Auxiliary definitions -- Reverse function composition and arrow plumbing aids ( # ), -- :: (a -> b) -> (b -> c) -> (a -> c), infixl 9 dup, -- :: a -> (a,a) ) where import Control.Arrow import FRP.Yampa.InternalCore import FRP.Yampa.Basic import FRP.Yampa.Conditional import FRP.Yampa.Delays import FRP.Yampa.Event import FRP.Yampa.EventS import FRP.Yampa.Hybrid import FRP.Yampa.Integration import FRP.Yampa.Loop import FRP.Yampa.Miscellany (( # ), dup) import FRP.Yampa.Random import FRP.Yampa.Scan import FRP.Yampa.Simulation import FRP.Yampa.Switches import FRP.Yampa.Time import FRP.Yampa.VectorSpace -- Vim modeline -- vim:set tabstop=8 expandtab: