{-# LANGUAGE TypeOperators, ScopedTypeVariables , FlexibleInstances, MultiParamTypeClasses , GeneralizedNewtypeDeriving #-} {-# OPTIONS_GHC -Wall -fno-warn-orphans #-} -- For ghc-6.6 compatibility -- {-# OPTIONS_GHC -fglasgow-exts -Wall #-} ---------------------------------------------------------------------- -- | -- Module : FRP.Reactive.PrimReactive -- Copyright : (c) Conal Elliott 2007 -- License : BSD3 -- -- Maintainer : conal@conal.net -- Stability : experimental -- -- Functional /events/ and /reactive values/. Semantically, an 'Event' is -- stream of future values in time order. A 'Reactive' value is a -- discretly time-varying value. -- -- Many of the operations on events and reactive values are packaged as -- instances of the standard type classes 'Monoid', 'Functor', -- 'Applicative', and 'Monad'. -- -- This module focuses on representation and primitives defined in terms -- of the representation. See also "FRP.Reactive.Reactive", which -- re-exports this module, plus extras that do not exploit the -- representation. My intention for this separation is to ease -- experimentation with alternative representations. -- -- Although the basic 'Reactive' type describes /discretely/-changing -- values, /continuously/-changing values can be modeled simply as -- reactive functions. See "FRP.Reactive.Behavior" for a convenient type -- composition of 'Reactive' and a constant-optimized representation of -- functions of time. The exact packaging of discrete vs continuous will -- probably change with more experience. ---------------------------------------------------------------------- module FRP.Reactive.PrimReactive ( -- * Events and reactive values EventG, ReactiveG -- * Operations on events and reactive values , stepper, switcher, withTimeGE, withTimeGR , futuresE, listEG, atTimesG, atTimeG , snapshotWith, accumE, accumR, once , withRestE, untilE , justE, filterE -- , traceE, traceR -- , mkEvent, mkEventTrace, mkEventShow , eventOcc -- * To be moved elsewhere , joinMaybes, filterMP, result -- * To be removed when it gets used somewhere , isMonotoneR -- * Testing , batch, infE ) where import Prelude hiding (zip) import Data.Monoid import Control.Applicative import Control.Arrow import Control.Monad import Data.Function (on) -- import Debug.Trace (trace) import Control.Comonad -- TODO: eliminate the needs for this stuff. import Control.Concurrent (threadDelay) import Control.Exception (evaluate) import System.IO.Unsafe import Test.QuickCheck hiding (evaluate) import Test.QuickCheck.Instances import Test.QuickCheck.Checkers import Test.QuickCheck.Classes -- import Data.List -- TypeCompose import Control.Compose ((:.)(..), inO2, Monoid_f(..)) import Data.Zip import Control.Instances () -- Monoid (IO ()) import Data.Unamb (race) import Data.Max import Data.AddBounds import FRP.Reactive.Future hiding (batch) import FRP.Reactive.Internal.Reactive {-------------------------------------------------------------------- Events and reactive values --------------------------------------------------------------------} -- Bogus EqProp instance. TODO: replace with a random equality test, such -- that the collection of all generated tests covers equality. instance (Eq a, Eq b, EqProp a, EqProp b) => EqProp (EventG a b) where a =-= b = foldr (.&.) (property True) $ zipWith (=-=) (f a) (f b) where f = take 20 . eFutures arbitraryE :: (Num t, Ord t, Arbitrary t, Arbitrary u) => Gen (EventG t u) arbitraryE = frequency [ (1, liftA2 ((liftA. liftA) futuresE addStart) arbitrary futureList) , (4, liftA futuresE futureList) ] where earliestFuture = Future . (,) (Max MinBound) addStart = (:).earliestFuture futureList = frequency [(10, futureListFinite), (1,futureListInf)] futureListFinite = liftA2 (zipWith future) nondecreasing arbitrary futureListInf = liftA2 (zipWith future) (resize 10 nondecreasingInf) (infiniteList arbitrary) instance (Arbitrary t, Ord t, Num t, Arbitrary a) => Arbitrary (EventG t a) where arbitrary = arbitraryE coarbitrary = coarbitrary . eFuture ---- -- Arbitrary works just like pairs: instance (Arbitrary t, Arbitrary a, Num t, Ord t) => Arbitrary (ReactiveG t a) where arbitrary = liftA2 Stepper arbitrary arbitrary coarbitrary (a `Stepper` e) = coarbitrary e . coarbitrary a instance Ord t => Model (ReactiveG t a) (t -> a) where model = rat instance (Ord t, Arbitrary t, Show t, EqProp a) => EqProp (ReactiveG t a) where (=-=) = (=-=) `on` model -- Initial value of a 'Reactive' rInit :: ReactiveG t a -> a rInit (a `Stepper` _) = a {-------------------------------------------------------------------- Instances --------------------------------------------------------------------} instance Ord t => Monoid (EventG t a) where mempty = Event mempty mappend = inEvent2 merge -- Standard instance for Applicative of Monoid instance (Ord t, Monoid a) => Monoid (ReactiveG t a) where mempty = pure mempty mappend = liftA2 mappend -- | Merge two 'Future' streams into one. merge :: Ord t => Binop (FutureG t (ReactiveG t a)) -- The following two lines seem to be too strict and are causing -- reactive to lock up. I.e. the time argument of one of these -- must have been _|_, so when we pattern match against it, we -- block. -- -- On the other hand, they patch a massive space leak in filterE. Perhaps -- there's an unamb solution. Future (Max MaxBound,_) `merge` v = v u `merge` Future (Max MaxBound,_) = u u `merge` v = (inFutR (`merge` v) <$> u) `mappend` (inFutR (u `merge`) <$> v) -- What's going on in this 'merge' definition? Try two different -- future paths. If u arrives before v (or simultaneously), then -- begin as u begins and then merge v with the rest of u. Otherwise, -- begin as v begins and then merge u with the rest of v. Because of -- the left-bias, make sure u fragments are always the first argument -- to merge and v fragments are always the second. -- Define functor instances in terms of each other. instance Functor (EventG t) where fmap = inEvent.fmap.fmap instance Functor (ReactiveG t) where fmap f ~(a `Stepper` e) = f a `stepper` fmap f e -- standard instance instance Ord t => Applicative (EventG t) where pure = return _ <*> (Event (Future (Max MaxBound,_))) = mempty x <*> y = x `ap` y -- standard instance instance Ord t => Alternative (EventG t) where { empty = mempty; (<|>) = mappend } instance Ord t => Zip (ReactiveG t) where -- zip :: ReactiveG t a -> ReactiveG t b -> ReactiveG t (a,b) (c `Stepper` ce) `zip` (d `Stepper` de) = (c,d) `accumR` pairEdit (ce,de) instance Ord t => Applicative (ReactiveG t) where pure a = a `stepper` mempty -- Standard definition. See 'Zip'. rf <*> rx = uncurry ($) <$> (rf `zip` rx) -- A wonderful thing about the <*> definition for ReactiveG is that it -- automatically caches the previous value of the function or argument -- when the argument or function changes. instance Ord t => Monad (EventG t) where return a = Event (pure (pure a)) e >>= f = joinE (fmap f e) -- happy a t b. Same as (a `mappend` b) except takes advantage of knowledge -- that t is a lower bound for the occurences of b. This allows for extra -- laziness. happy :: (Ord t) => EventG t a -> Time t -> EventG t a -> EventG t a happy a (Max MaxBound) _ = a happy (Event (Future (Max MaxBound, _))) _ b = b happy a@(Event (Future (t0, e `Stepper` ee'))) t b | t0 <= t = (Event (Future (t0, e `Stepper` (happy ee' t b)))) | otherwise = a `mappend` b -- Note, joinE should not be called with an infinite list of events that all -- occur at the same time. It can't decide which occurs first. joinE :: (Ord t) => EventG t (EventG t a) -> EventG t a joinE (Event (Future (Max MaxBound, _))) = mempty joinE (Event (Future (t0h, e `Stepper` ((Event (Future (Max MaxBound, _))))))) = adjustE t0h e joinE (Event (Future (t0h, e `Stepper` ee'@((Event (Future (t1h, _))))))) = happy (adjustE t0h e) t1h (adjustTopE t0h (joinE ee')) -- Original Version: -- joinE (Event (Future (t0h, e `Stepper` ee'))) = -- adjustE t0h e `mappend` adjustTopE t0h (joinE ee') adjustTopE :: Ord t => Time t -> EventG t t1 -> EventG t t1 adjustTopE t0h = (inEvent.inFuture.first) (max t0h) -- adjustTopE t0h (Event (Future (tah, r))) = -- Event (Future (t0h `max` tah,r)) adjustE :: Ord t => Time t -> EventG t t1 -> EventG t t1 adjustE _ e@(Event (Future (Max MaxBound, _))) = e adjustE t0h (Event (Future (tah, a `Stepper` e))) = Event (Future (t1h,a `Stepper` adjustE t1h e)) where t1h = t0h `max` tah -- The two-caseness of adjustE prevents the any info from coming out until -- tah is known to be Max or non-Max. Problem? -- Is the MaxBound case really necessary? -- TODO: add adjustE explanation. What's going on and why t1 in the -- recursive call? David's comment: -- If we have an event [t1, t2] we know t2 >= t1 so (max t t2) == (max (max t t1) t2). -- See http://hpaste.org/11518 for a def that doesn't change the lower bound. -- -- What I remember is that this function is quite subtle w.r.t laziness. -- There are some notes in the paper. If i find instead that a simpler -- definition is possible, so much the better. -- Here's an alternative to joinE that is less strict, and doesn't cause -- reactive to lock up. Need to verify correctness. (Does lock up with -- the mappend optimization that eliminates a space/time leak.) {- joinE :: Ord t => EventG t (EventG t a) -> EventG t a joinE (Event (Future (t0h, ~(e `Stepper` ee')))) = adjustE t0h (e `mappend` joinE ee') adjustE t0h (Event (Future (tah, ~(a `Stepper` e)))) = Event (Future (t1h,a `Stepper` adjustE t1h e)) where t1h = t0h `max` tah -} -- From Jules Bean (quicksilver): -- joinE :: (Ord t) => EventG t (EventG t a) -> EventG t a -- joinE (Event u) = -- Event . join $ -- fmap (\ (e `Stepper` ee) -> -- let (Event uu) = (e `mappend` joinE ee) in uu) -- u -- plus some fiddling: -- joinE :: (Ord t) => EventG t (EventG t a) -> EventG t a -- joinE = inEvent (>>= g) -- where -- g ~(e `Stepper` ee) = eFuture (e `mappend` joinE ee) -- These two joinE defs both lock up in my tests. instance Ord t => MonadPlus (EventG t) where { mzero = mempty; mplus = mappend } -- Standard instance for Applicative w/ join instance Ord t => Monad (ReactiveG t) where return = pure r >>= f = joinR (f <$> r) -- | Pass through the 'Just' occurrences, stripped. Experimental -- specialization of 'joinMaybes'. justE :: Ord t => EventG t (Maybe a) -> EventG t a justE (Event (Future (ta, Just a `Stepper` e'))) = Event (Future (ta, a `Stepper` justE e')) justE (Event (Future (ta, Nothing `Stepper` e'))) = adjustE ta (justE e') -- The adjustE lets consumers know that the resulting event occurs no -- earlier than ta. -- | Pass through values satisfying a given predicate. Experimental -- specialization of 'filterMP'. filterE :: (Ord t, Show a) => (a -> Bool) -> EventG t a -> EventG t a -- filterE p e = joinMaybes (f <$> e) -- where -- f a | p a = Just a -- | otherwise = Nothing filterE _ e@(Event (Future (Max MaxBound, _))) = e filterE p (Event (Future (ta, a `Stepper` e'))) = h (filterE p e') where h | p a = -- trace ("pass " ++ show a) $ \ e'' -> Event (Future (ta, a `Stepper` e'')) | otherwise = -- trace ("skip " ++ show a) $ adjustTopE ta -- Or maybe move the adjustTopE to the second filterE -- adjustTopE t0h = (inEvent.inFuture.first) (max t0h) -- Laziness problem: no information at all can come out of filterE's -- result until @p a@ is known. -- filterE p ~(Event (Future (ta, a `Stepper` e'))) = -- Event (Future (ta', r')) -- where -- ta' -- -- if p a then -- Event (Future (ta, a `Stepper` filterE p e')) -- else -- adjustE ta (filterE p e') {-------------------------------------------------------------------- Operations on events and reactive values --------------------------------------------------------------------} -- | Reactive value from an initial value and a new-value event. stepper :: a -> EventG t a -> ReactiveG t a stepper = Stepper -- -- | Turn a reactive value into an event, with the initial value -- -- occurring at -Infinity. -- -- -- -- Oops: breaks the semantic abstraction of 'Reactive' as a step -- function. -- rToE :: Ord t => ReactiveG t a -> EventG t a -- rToE (a `Stepper` e) = pure a `mappend` e -- | Switch between reactive values. switcher :: Ord t => ReactiveG t a -> EventG t (ReactiveG t a) -> ReactiveG t a r `switcher` e = join (r `stepper` e) -- | Reactive 'join' (equivalent to 'join' but slightly more efficient, I think) joinR :: Ord t => ReactiveG t (ReactiveG t a) -> ReactiveG t a joinR ((a `Stepper` Event ur) `Stepper` e'@(Event urr)) = a `stepper` Event u where u = ((`switcher` e') <$> ur) `mappend` (join <$> urr) -- The following simpler definition is wrong. It keeps listening to @e@ -- even after @er@ has occurred. -- joinR ((a `Stepper` e) `Stepper` er) = -- a `stepper` (e `mappend` join (rToE <$> er)) -- e :: EventG t a -- er :: EventG t (ReactiveG t a) -- -- rToE <$> er ::: EventG t (EventG t a) -- join (rToE <$> er) ::: EventG t a -- | Access occurrence times in an event. See also 'withTimeGR'. withTimeGE :: EventG t a -> EventG t (a, Time t) withTimeGE = inEvent $ inFuture $ \ (t,r) -> (t, withTimeGR t r) -- | Access occurrence times in a reactive value. See also 'withTimeGE'. withTimeGR :: Time t -> ReactiveG t a -> ReactiveG t (a, Time t) withTimeGR t (a `Stepper` e) = (a,t) `Stepper` withTimeGE e -- | Convert a temporally monotonic list of futures to an event. See also -- the specialization 'listE' listEG :: Ord t => [(t,a)] -> EventG t a listEG = futuresE . map (uncurry future) -- | Convert a temporally monotonic list of futures to an event futuresE :: Ord t => [FutureG t a] -> EventG t a futuresE [] = mempty futuresE (Future (t,a) : futs) = -- trace ("l2E: "++show t) $ Event (Future (t, a `stepper` futuresE futs)) -- TODO: redefine 'futuresE' as a fold -- futuresE = foldr (\ fut e -> Event ((`stepper` e) <$> fut)) mempty -- TODO: hide futuresE. currently exported for use in TVal. If I move to -- Internal/Reactive, I have to move the monoid instance there, which -- requires moving others as well. -- | Event at given times. See also 'atTimeG'. atTimesG :: Ord t => [t] -> EventG t () atTimesG = listEG . fmap (flip (,) ()) -- | Single-occurrence event at given time. atTimeG :: Ord t => t -> EventG t () atTimeG = atTimesG . pure -- This variant of 'snapshot' has 'Nothing's where @b@ changed and @a@ -- didn't. snap :: forall a b t. Ord t => EventG t a -> ReactiveG t b -> EventG t (Maybe a, b) ea `snap` (b0 `Stepper` eb) = (Nothing, b0) `accumE` (fmap fa ea `mappend` fmap fb eb) where fa :: a -> Unop (Maybe a, b) fb :: b -> Unop (Maybe a, b) fa a (_,b) = (Just a , b) fb b _ = (Nothing, b) -- | Snapshot a reactive value whenever an event occurs and apply a -- combining function to the event and reactive's values. snapshotWith :: Ord t => (a -> b -> c) -> EventG t a -> ReactiveG t b -> EventG t c snapshotWith f e r = joinMaybes $ fmap h (e `snap` r) where h (Nothing,_) = Nothing h (Just a ,b) = Just (f a b) -- | Accumulating event, starting from an initial value and a -- update-function event. See also 'accumR'. accumE :: a -> EventG t (a -> a) -> EventG t a accumE a = inEvent $ fmap $ \ (f `Stepper` e') -> f a `accumR` e' -- | Reactive value from an initial value and an updater event. See also -- 'accumE'. accumR :: a -> EventG t (a -> a) -> ReactiveG t a a `accumR` e = a `stepper` (a `accumE` e) -- | Just the first occurrence of an event. once :: Ord t => EventG t a -> EventG t a once = (inEvent.fmap) (pure . rInit) -- | Extract a future representing the first occurrence of the event together -- with the event of all occurrences after that one. eventOcc :: (Ord t) => EventG t a -> FutureG t (a, EventG t a) eventOcc (Event fut) = (\ (Stepper a e) -> (a,e)) <$> fut -- | Access the remainder with each event occurrence. withRestE :: EventG t a -> EventG t (a, EventG t a) withRestE = (inEvent.fmap) $ \ (a `Stepper` e') -> (a,e') `stepper` withRestE e' -- | Truncate first event at first occurrence of second event. untilE :: Ord t => EventG t a -> EventG t b -> EventG t a ea `untilE` Event (Future ~(tb,_)) = ea `untilET` tb -- | Truncate first event at the given time. untilET :: Ord t => EventG t a -> Time t -> EventG t a -- Event (Future (ta, ~(a `Stepper` e'))) `untilET` t = -- if ta < t then -- Event (Future (ta, a `Stepper` (e' `untilET` t))) -- else -- mempty -- Hm. I doubt that the definition above gives sufficient temporal -- laziness. No information can come out of the result until the value of -- @ta < t@ is determined, which is usually at about time @ta `min` t@. -- So, try the following definition instead. It immediately provides -- lower bounds of both @ta@ and @t@ as lower bounds of the constructed -- event occurrences. Event (Future ~(ta, a `Stepper` e')) `untilET` t = Event (Future (ta', a `Stepper` (e' `untilET` t))) where ta' = (ta `min` t) `max` (if ta < t then ta else maxBound) -- I'm not sure about @<@ vs @<=@ above. -- | Sample a reactive value at a sequence of monotonically non-decreasing -- times. Deprecated, because it does not reveal when value is known to -- be repeated in the output. Those values won't be recomputed, but they -- may be re-displayed. rats :: Ord t => ReactiveG t a -> [t] -> [a] -- increasing times _ `rats` [] = [] r@(a `Stepper` Event (Future (tr',r'))) `rats` ts@(t:ts') | ftime t <= tr' = a : r `rats` ts' | otherwise = r' `rats` ts -- Just for testing rat :: Ord t => ReactiveG t a -> t -> a rat r = head . rats r . (:[]) {-------------------------------------------------------------------- Other instances --------------------------------------------------------------------} -- Standard instances instance (Monoid_f f, Ord t) => Monoid_f (ReactiveG t :. f) where { mempty_f = O (pure mempty_f); mappend_f = inO2 (liftA2 mappend_f) } instance (Ord t, Zip f) => Zip (ReactiveG t :. f) where zip = apZip instance Unzip (ReactiveG t) where {fsts = fmap fst; snds = fmap snd} -- Standard instances instance Ord t => Monoid_f (EventG t) where { mempty_f = mempty ; mappend_f = mappend } instance Ord t => Monoid ((EventG t :. f) a) where { mempty = O mempty; mappend = inO2 mappend } instance Ord t => Monoid_f (EventG t :. f) where { mempty_f = mempty ; mappend_f = mappend } instance (Ord t, Cozip f) => Zip (EventG t :. f) where zip = cozip -- Standard instance for functors instance Unzip (EventG t) where {fsts = fmap fst; snds = fmap snd} {-------------------------------------------------------------------- Comonadic stuff --------------------------------------------------------------------} instance Monoid t => Copointed (EventG t) where -- E a -> F (R a) -> R a -> a extract = extract . extract . eFuture -- Here's the plan for 'duplicate': -- -- E a -> F (R a) -> F (R (R a)) -> F (F (R (R a))) -- -> F (R (F (R a))) -> E (F (R a)) -> E (E a) instance Monoid t => Comonad (EventG t) where duplicate = fmap Event . Event . fmap frTOrf . duplicate . fmap duplicate . eFuture -- This frTOrf definition type-checks. Is it what we want? frTOrf :: FutureG t (ReactiveG t a) -> ReactiveG t (FutureG t a) frTOrf ~(Future (ta,e)) = (Future . (,) ta) <$> e -- TODO: Reconsider E = F :. R . Didn't work with absolute time. What -- about relative time? instance Ord t => Pointed (ReactiveG t) where point = (`stepper` mempty) -- TODO: I think we can bypass mempty and so eliminate the Ord -- constraint. If so, remove Ord tr from 'time' in Behavior. instance Monoid t => Copointed (ReactiveG t) where -- extract = extract . rat -- Semantically: extract == extract . rat == (`rat` mempty) But mempty -- is the earliest time (since I'm using the Max monoid *), so here's a -- cheap alternative that also doesn't require Ord t: extract (a `Stepper` _) = a -- extract r == extract (rat r) == rat r mempty -- * Moreover, mempty is the earliest time in the Sum monoid on -- non-negative values, for relative-time behaviors. instance Monoid t => Comonad (ReactiveG t) where duplicate r@(_ `Stepper` Event u) = r `Stepper` Event (duplicate <$> u) -- TODO: Prove the morphism law: -- -- fmap rat . rat . dup == dup . rat -- Reactive is like the stream comonad -- TODO: try again letting events and reactives be streams of futures. {-------------------------------------------------------------------- To be moved elsewhere --------------------------------------------------------------------} -- | Pass through @Just@ occurrences. joinMaybes :: MonadPlus m => m (Maybe a) -> m a joinMaybes = (>>= maybe mzero return) -- | Pass through values satisfying @p@. filterMP :: MonadPlus m => (a -> Bool) -> m a -> m a filterMP p m = joinMaybes (liftM f m) where f a | p a = Just a | otherwise = Nothing -- Alternatively: -- filterMP p m = m >>= guarded p -- where -- guarded p x = guard (p x) >> return x -- | Apply a given function inside the results of other functions. -- Equivalent to '(.)', but has a nicer reading when composed result :: (b -> b') -> ((a -> b) -> (a -> b')) result = (.) {-------------------------------------------------------------------- Tests --------------------------------------------------------------------} -- TODO: Define more types like ApTy, use in batch below. Move to checkers. type ApTy f a b = f (a -> b) -> f a -> f b batch :: TestBatch batch = ( "Reactive.PrimReactive" , concatMap unbatch [ ("monotonicity", [ monotonicity2 "<*>" ((<*>) :: ApTy (EventG NumT) T T) , monotonicity2 "adjustE" (adjustE :: Time NumT -> EventG NumT NumT -> EventG NumT NumT) , monotonicity "join" (join :: EventG NumT (EventG NumT T) -> EventG NumT T) , monotonicity "withTimeGE" (withTimeGE :: EventG NumT T -> EventG NumT (T, Time NumT)) , monotonicity "once" (once :: EventG NumT T -> EventG NumT T) , monotonicity2 "accumE" (accumE :: T -> EventG NumT (T -> T) -> EventG NumT T) , monotonicity2 "mappend" (mappend :: EventG NumT T -> EventG NumT T -> EventG NumT T) , monotonicity2 "mplus" (mplus :: EventG NumT T -> EventG NumT T -> EventG NumT T) , monotonicity2 "<|>" ((<|>) :: EventG NumT T -> EventG NumT T -> EventG NumT T) , monotonicity2 "fmap" (fmap :: (T -> T) -> EventG NumT T -> EventG NumT T) -- ,monotonicity2 "flip (>>=)" (flip (>>=)) -- ,monotonicity2 (flip snapshot) "flip snapshot" ]) , ("order preservation", [ simulEventOrder "once" (once :: EventG NumT NumT -> EventG NumT NumT) ]) -- monad associativity fails -- , monad (undefined :: EventG NumT (NumT,T,NumT)) , monad (undefined :: ReactiveG NumT (NumT,T,NumT)) , monoid (undefined :: EventG NumT T) , monoid (undefined :: ReactiveG NumT [T]) -- , ("occurance count", -- [("joinE", joinEOccuranceCount)] -- ) ] ) -- joinEOccuranceCount :: Property -- joinEOccuranceCount = -- forAll (finiteEvent $ finiteEvent arbitrary -- :: Gen (EventG NumT (EventG NumT T))) -- ((==) <$> (sum . map (length . toListE_) . toListE_) -- <*> (length . toListE_ . joinE)) {- toListE :: EventG t a -> [FutureG t a] toListE (Event (Future (Max MaxBound, _ ))) = [] toListE (Event (Future (t0 , v `Stepper` e'))) = Future (t0,v) : toListE e' toListE_ :: EventG t a -> [a] toListE_ = map futVal . toListE -} monotonicity :: (Show a, Arbitrary a, Arbitrary t ,Num t, Ord t, Ord t') => String -> (EventG t a -> EventG t' a') -> (String,Property) monotonicity n f = (n, property $ monotoneTest f) monotonicity2 :: (Show a, Show b, Arbitrary a, Arbitrary b, Arbitrary t ,Num t, Ord t, Ord t') => String -> (b -> EventG t a -> EventG t' a') -> (String,Property) monotonicity2 n f = (n, property $ monotoneTest2 f) monotoneTest :: (Ord t') => (EventG t a -> EventG t' a') -> EventG t a -> Bool monotoneTest f e = unsafePerformIO ( (evaluate (isMonotoneE . f $ e)) `race` slowTrue) monotoneTest2 :: (Show a, Show b, Arbitrary a, Arbitrary b, Arbitrary t ,Num t, Ord t, Ord t') => (b -> EventG t a -> EventG t' a') -> (b , EventG t a) -> Bool monotoneTest2 f (x,e) = unsafePerformIO ( (evaluate (isMonotoneE (x `f` e))) `race` slowTrue) slowTrue :: IO Bool slowTrue = do threadDelay 10 return True -- TODO: Replace this stuff with a use of delay from Data.Later in checkers. isMonotoneE :: (Ord t) => EventG t a -> Bool isMonotoneE = liftA2 (||) ((==(Max MaxBound)) . futTime . eFuture) ((uncurry isMonotoneR') . unFuture . eFuture) isMonotoneE' :: (Ord t) => (Time t) -> EventG t a -> Bool isMonotoneE' t = liftA2 (||) ((==(Max MaxBound)) . futTime . eFuture) ((\(t',r) -> t <= t' && isMonotoneR' t' r) . unFuture . eFuture) isMonotoneR :: (Ord t) => ReactiveG t a -> Bool isMonotoneR (_ `Stepper` e) = isMonotoneE e isMonotoneR' :: (Ord t) => (Time t) -> ReactiveG t a -> Bool isMonotoneR' t (_ `Stepper` e) = isMonotoneE' t e simulEventOrder :: (Arbitrary t, Num t, Ord t ,Arbitrary t', Num t', Ord t' ,Num t'', Ord t'', Num t''', Ord t''') => String -> (EventG t t' -> EventG t'' t''') -> (String, Property) simulEventOrder n f = (n,forAll genEvent (isStillOrderedE . f)) where genEvent :: (Arbitrary t1, Num t1, Ord t1, Arbitrary t2, Num t2, Ord t2) => Gen (EventG t1 t2) genEvent = liftA futuresE (liftA2 (zipWith future) nondecreasing increasing) isStillOrderedE :: (Num t1, Ord t1, Num t2, Ord t2) => EventG t1 t2 -> Bool isStillOrderedE = liftA2 (||) ((==(Max MaxBound)) . futTime . eFuture) (isStillOrderedR . futVal . eFuture) isStillOrderedR (a `Stepper` e) = isStillOrderedE' a e isStillOrderedE' a = liftA2 (||) ((==(Max MaxBound)) . futTime . eFuture) (isStillOrderedR' a . futVal . eFuture) isStillOrderedR' a (b `Stepper` e) = a < b && isStillOrderedE' b e -- An infinite event. handy for testing. infE :: EventG NumT NumT infE = futuresE (zipWith future [1..] [1..])