reactive 0.3 → 0.5
raw patch · 14 files changed
+900/−26 lines, 14 filesdep ~TypeCompose
Dependency ranges changed: TypeCompose
Files
- changes.tw +17/−0
- reactive.cabal +10/−3
- src/Data/EventExtras.hs +107/−0
- src/Data/Fun.hs +6/−0
- src/Data/Future.hs +2/−2
- src/Data/Improving.hs +53/−0
- src/Data/MEvent.hs +72/−0
- src/Data/Reactive.hs +21/−8
- src/Data/SEvent.hs +168/−0
- src/Data/SFuture.hs +64/−12
- src/Data/SImproving.hs +60/−0
- src/Data/SReactive.hs +169/−0
- src/Data/Unamb.hs +83/−0
- src/Examples.hs +68/−1
changes.tw view
@@ -1,5 +1,22 @@ == Version 0 == +=== Version 0.5 ===++Experiments toward a radically new implementation. Active code is unaffected. (I hope!)++* commented out experimental modules (breaking TypeCompose 0.4 DistribM dependency)+* new Improving.hs (old is SImproving.hs), Unamb.+* Generalized Reactive to Reactive'+* structural tweaking with EventExtras+* factored out EventExtras for use with different Events reps.+* many additions to SReactive+* SFuture tweak. redid mergeF in SEvent.+* Data.SReactive+* Event' in event ops in MEvent.hs+* MEvent.hs: more event ops+* lots of little experiments+* race condition fixed in the race function in Data.Future+ === Version 0.3 === * Commented out LANGUAGE pragmas and added OPTIONS_GHC -fglasgow-exts for ghc-6.6 compatibility.
reactive.cabal view
@@ -1,5 +1,5 @@ Name: reactive-Version: 0.3+Version: 0.5 Synopsis: Simple foundation for functional reactive programming Category: reactivity, FRP Description:@@ -23,13 +23,20 @@ Copyright: (c) 2007-2008 by Conal Elliott License: BSD3 Stability: provisional+build-type: Simple Hs-Source-Dirs: src Extensions: -Build-Depends: base, TypeCompose+Build-Depends: base, TypeCompose>=0.3 Exposed-Modules: Data.SFuture Data.Future Data.Fun Data.Reactive Extra-Source-Files:-ghc-options: -Wall -O+ghc-options: -Wall++-- Experimental modules:+-- Data.SEvent+-- Data.MEvent+-- Data.EventExtras+-- Data.SReactive
+ src/Data/EventExtras.hs view
@@ -0,0 +1,107 @@+{-# OPTIONS_GHC -Wall #-}+----------------------------------------------------------------------+-- |+-- Module : Data.EventExtras+-- Copyright : (c) Conal Elliott 2008+-- License : BSD3+-- +-- Maintainer : conal@conal.net+-- Stability : experimental+-- +-- Event "extras", i.e., independent of representation+----------------------------------------------------------------------++module Data.EventExtras+ (+ module Data.SEvent+ -- * Event extras+ , EventD, EventI+ , traceE, pairE, scanlE, monoidE+ , withPrevE, countE, countE_, diffE+ -- * To be moved elsewhere+ , joinMaybes, filterMP+ ) where++import Control.Monad (liftM)+import Control.Applicative ((<$>),liftA2)+import Data.Pair (pairEdit)+import Data.Monoid+import Control.Monad (MonadPlus(..))+import Debug.Trace (trace)++import Data.SEvent+-- import Data.MEvent++import Data.Improving++-- | Event, using Double for time+type EventD = Event' Double++-- | Event, using an /improving/ double for time+type EventI = Event' (Improving Double)++-- | Tracing of events.+traceE :: (a -> String) -> Event' t a -> Event' t a+traceE shw = fmap (\ a -> trace (shw a) a)++pairE :: Ord t => (c,d) -> (Event' t c, Event' t d) -> Event' t (c,d)+pairE cd cde = cd `accumE` pairEdit cde++-- | Like 'scanl' for events. See also 'scanlR'.+scanlE :: Ord t => (a -> b -> a) -> a -> Event' t b -> Event' t a+scanlE f a e = a `accumE` (flip f <$> e)++-- | Accumulate values from a monoid-valued event. Specialization of+-- 'scanlE', using 'mappend' and 'mempty'. See also 'monoidR'.+monoidE :: (Ord t, Monoid o) => Event' t o -> Event' t o+monoidE = scanlE mappend mempty++-- | Pair each event value with the previous one, given an initial value.+withPrevE :: Ord t => Event' t a -> Event' t (a,a)+withPrevE e = (joinMaybes . fmap combineMaybes) $+ (Nothing,Nothing) `accumE` fmap (shift.Just) e+ where+ -- Shift newer value into (old,new) pair if present.+ shift :: u -> (u,u) -> (u,u)+ shift new (_,old) = (old,new)+ combineMaybes :: (Maybe u, Maybe v) -> Maybe (u,v)+ combineMaybes = uncurry (liftA2 (,))++-- | Count occurrences of an event, remembering the occurrence values.+-- See also 'countE_'.+countE :: (Ord t, Num n) => Event' t b -> Event' t (b,n)+countE = scanlE h (b0,0)+ where+ b0 = error "withCountE: no initial value"+ h (_,n) b = (b,n+1)++-- | Count occurrences of an event, forgetting the occurrence values. See+-- also 'countE'. See also 'countR'.+countE_ :: (Ord t, Num n) => Event' t b -> Event' t n+countE_ e = snd <$> countE e++-- | Difference of successive event occurrences.+diffE :: (Ord t, Num n) => Event' t n -> Event' t n+diffE e = uncurry (-) <$> withPrevE e+++{--------------------------------------------------------------------+ 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+
src/Data/Fun.hs view
@@ -13,6 +13,7 @@ module Data.Fun (Fun(..), apply) where +import Data.Monoid (Monoid(..)) import Control.Applicative (Applicative(..)) import Control.Arrow hiding (pure) @@ -24,6 +25,11 @@ apply :: Fun t a -> (t -> a) apply (K a) = const a apply (Fun f) = f++instance Monoid a => Monoid (Fun t a) where+ mempty = K mempty+ K a `mappend` K a' = K (a `mappend` a')+ funa `mappend` funb = Fun (apply funa `mappend` apply funb) instance Functor (Fun t) where fmap f (K a) = K (f a)
src/Data/Future.hs view
@@ -1,7 +1,6 @@ -- {-# LANGUAGE RecursiveDo #-} -- For ghc-6.6 compatibility {-# OPTIONS_GHC -fglasgow-exts #-}-{-# OPTIONS -fno-warn-orphans #-} ---------------------------------------------------------------------- -- |@@ -152,7 +151,9 @@ a `race` Never = a a `race` b = unsafePerformIO $ do (c,sink) <- newFuture+ lock <- newEmptyMVar -- to avoid double-kill let run fut tid = forkIO $ do x <- force fut+ putMVar lock () killThread tid sink x mdo ta <- run a tb@@ -168,4 +169,3 @@ -- | Run an 'IO'-action-valued 'Future'. runFuture :: Future (IO ()) -> IO () runFuture = join . force-
+ src/Data/Improving.hs view
@@ -0,0 +1,53 @@+----------------------------------------------------------------------+-- |+-- Module : Data.Improving+-- Copyright : (c) Conal Elliott 2008+-- License : BSD3+-- +-- Maintainer : conal@conal.net+-- Stability : experimental+-- +-- Improving values -- efficient version+----------------------------------------------------------------------++module Data.Improving+ (+ Improving(..), minI+ ) where+++import Data.Unamb (unamb,assuming)++{----------------------------------------------------------+ Improving values+----------------------------------------------------------}++-- | An improving value.+data Improving a = IV a (a -> Ordering)++-- | A known improving value (which doesn't really improve)+exactly :: Ord a => a -> Improving a+exactly a = IV a (compare a)++instance Eq a => Eq (Improving a) where+ IV a _ == IV b _ = a == b++instance Ord a => Ord (Improving a) where+ s `min` t = fst (s `minI` t)+ s <= t = snd (s `minI` t)++-- | Efficient combination of 'min' and '(<=)'+minI :: Ord a => Improving a -> Improving a -> (Improving a,Bool)+IV u uComp `minI` IV v vComp = (IV uMinV wComp, uLeqV)+ where+ uMinV = if uLeqV then u else v+ -- u <= v: Try @v `compare` u /= LT@ and @u `compare` v /= GT@.+ uLeqV = (vComp u /= LT) `unamb` (uComp v /= GT)+ -- (u `min` v) `compare` t: Try comparing according to whether u <= v,+ -- or go with either answer if they agree, e.g., if both say GT.+ minComp = if uLeqV then uComp else vComp+ wComp t = minComp t `unamb`+ assuming (uCompT == vCompT) uCompT+ where+ uCompT = uComp t+ vCompT = vComp t
+ src/Data/MEvent.hs view
@@ -0,0 +1,72 @@+{-# OPTIONS_GHC -Wall #-}+{-# LANGUAGE TypeOperators, TypeSynonymInstances, MultiParamTypeClasses #-}++----------------------------------------------------------------------+-- |+-- Module : Data.MEvent+-- Copyright : (c) Conal Elliott 2008+-- License : BSD3+-- +-- Maintainer : conal@conal.net+-- Stability : experimental+-- +-- Event implementation via semantics & Maybe+----------------------------------------------------------------------++module Data.MEvent+ (+ -- * Event primitives+ Event', accumE+ ) where++import Data.Monoid+import Data.Maybe+import Control.Monad (MonadPlus(..))++-- TypeCompose+import Control.Compose ((:.)(..),inO,inO2)++import qualified Data.SEvent as SR -- semantics+++{----------------------------------------------------------+ Event primitives+----------------------------------------------------------}++-- | General events. 'Functor', 'Applicative', and 'Monoid' by+-- construction. See also 'Event\''.+-- In this representation, an event is a list of time/maybe pairs. The+-- 'Just's correspond to occurrences and the 'Nothing's to+-- non-occurrences.+type Event' t = SR.Event' t :. Maybe++-- The 'Monad' instance is thanks to Data.SEvent:+-- +-- instance Ord t => DistribM (Event' t) Maybe where ...++-- TODO: revisit Phooey. Can I use :. in place of monad transformers?+-- How to monad transformers relate to the monad instance of (:.)?+-- Follow up on references from my chat with Cale on 2008-03-02.++-- One of the standard Monoid instances for type compositions. This one+-- interleaves occurrences.+instance Ord t => Monoid (Event' t a) where+ mempty = O mempty+ mappend = inO2 mappend -- interleave++-- This MonadPlus instance could go in EventExtras, but it would be an+-- orphan there.+instance Ord t => MonadPlus (Event' t) where { mzero = mempty; mplus = mappend }+++-- | Accumulating event, starting from an initial value and a+-- update-function event.+accumE :: Ord t => a -> Event' t (a -> a) -> Event' t a+accumE a = inO $ fmap Just . SR.accumE a . fmap (fromMaybe id)++-- TODO: redefine accumE to preserve 'Nothing's, for later optimization.++-- (<*>) :: Fut (a->a) -> Fut a -> Fut a++-- (<*>) (on futures) does some unnecessary work here, since the function+-- is guaranteed to be at least as new as the argument.
src/Data/Reactive.hs view
@@ -5,7 +5,6 @@ -- For ghc-6.6 compatibility {-# OPTIONS_GHC -fglasgow-exts #-} - ---------------------------------------------------------------------- -- | -- Module : Data.Reactive@@ -60,8 +59,10 @@ import Control.Monad import Debug.Trace (trace) import Data.IORef-import Control.Concurrent (forkIO,ThreadId)+import Control.Concurrent -- (forkIO,ThreadId) +import Data.Maybe+ -- TypeCompose import Control.Compose (Unop,(:.)(..), inO2, Monoid_f(..)) import Data.Pair@@ -99,7 +100,9 @@ -- we're playing Asteroids and tracking collisions. Each collision can -- break an asteroid into more of them, each of which has to be tracked -- for more collisions. Another example: A chat room has an /enter/--- event, whose occurrences contain new events like /speak/.+-- event, whose occurrences contain new events like /speak/. An+-- especially useful monad-based function is 'joinMaybes', which filters a+-- Maybe-valued event. -- newtype Event a = Event { eFuture :: Future (Reactive a) } @@ -186,9 +189,18 @@ instance Applicative Reactive where pure a = a `stepper` mempty- rf@(f `Stepper` Event vf) <*> rx@(x `Stepper` Event vx) =- f x `stepper` Event (((<*> rx) <$> vf) `mappend` ((rf <*>) <$> vx))+ rf@(f `Stepper` Event futf) <*> rx@(x `Stepper` Event futx) =+ f x `stepper` Event fut+ where+ fut = fmap (\ rf' -> rf' <*> rx ) futf `mappend`+ fmap (\ rx' -> rf <*> rx') futx +-- More succinctly,+-- +-- rf@(f `Stepper` Event futf) <*> rx@(x `Stepper` Event futx) =+-- f x `stepper` Event (((<*> rx) <$> futf) `mappend` ((rf <*>) <$> futx))++ -- A wonderful thing about the <*> definition for Reactive is that it -- automatically caches the previous value of the function or argument -- when the argument or function changes.@@ -281,7 +293,7 @@ accumE :: a -> Event (a -> a) -> Event a accumE a = inEvent $ fmap $ \ (f `Stepper` e') -> f a `accumR` e' --- | Like 'scanl' for events. See also 'scanE'.+-- | Like 'scanl' for events. See also 'scanlR'. scanlE :: (a -> b -> a) -> a -> Event b -> Event a scanlE f a e = a `accumE` (flip f <$> e) @@ -373,11 +385,12 @@ mkReactive :: a -> IO (Reactive a, Sink a) mkReactive a0 = first (a0 `stepper`) <$> mkEvent --- | Reactive value from an initial value and an updater event. See also 'accumE'.+-- | Reactive value from an initial value and an updater event. See also+-- 'accumE'. accumR :: a -> Event (a -> a) -> Reactive a a `accumR` e = a `stepper` (a `accumE` e) --- | Like 'scanl' for reactive values. See also 'scanE'.+-- | Like 'scanl' for reactive values. See also 'scanlE'. scanlR :: (a -> b -> a) -> a -> Event b -> Reactive a scanlR f a e = a `stepper` scanlE f a e
+ src/Data/SEvent.hs view
@@ -0,0 +1,168 @@+{-# OPTIONS_GHC -Wall #-}+{-# LANGUAGE TypeOperators, GeneralizedNewtypeDeriving+ , FlexibleInstances, FlexibleContexts, TypeSynonymInstances+ , MultiParamTypeClasses+ #-}++----------------------------------------------------------------------+-- |+-- Module : Data.SEvent+-- Copyright : (c) Conal Elliott 2008+-- License : BSD3+-- +-- Maintainer : conal@conal.net+-- Stability : experimental+-- +-- Denotational semantics for events+----------------------------------------------------------------------++module Data.SEvent+ (+ -- * Event primitives+ Event'(..), accumE+ ) where++import Data.Monoid+import Control.Applicative+import Control.Monad++-- TypeCompose+import Control.Compose (Binop,DistribM(..))++import Data.SFuture+++{----------------------------------------------------------+ Event primitives+----------------------------------------------------------}++-- | Generalized 'Event' over arbitrary (ordered) time type. See also+-- 'Event\''. +newtype Event' t a = E { unE :: [Future t a] }++-- | Apply a unary function within the 'Ascend' constructor.+inE :: ([Future t a] -> [Future t b]) -> (Event' t a -> Event' t b)+inE f = E . f . unE++-- | Apply a binary function within the 'E' constructor.+inE2 :: ([Future t a] -> [Future t b] -> [Future t c])+ -> (Event' t a -> Event' t b -> Event' t c)+inE2 f = inE . f . unE+++-- Note: the semantics of Applicative and Monad are not consistent with+-- +-- type Event = [] :. Fut+-- +-- because that composition would combine future values with []-style+-- backtracking instead of temporal interleaving.+-- +-- However, maybe there's a []-wrapping newtype I can use instead.++instance Ord t => Monoid (Event' t a) where+ mempty = E []+ mappend = inE2 merge++instance Functor (Event' t) where+ fmap f = inE ((fmap.fmap) f)++instance Ord t => Applicative (Event' t) where+ pure = return+ (<*>) = ap++instance Ord t => Monad (Event' t) where+ return a = E ((return.return) a)+ e >>= f = joinE (f <$> e)++-- This MonadPlus instance could go in EventExtras, but it would be an+-- orphan there.+instance Ord t => MonadPlus (Event' t) where { mzero = mempty; mplus = mappend }++-- For monad compositions.+-- We'll need this instance in MEvent. It'd be an orphan there.+instance Ord t => DistribM (Event' t) Maybe where+ -- distribM :: Maybe (Event' t b) -> Event' t (Maybe b)+ distribM = maybe mempty (fmap Just)++-- | Equivalent to 'join' for 'Event'. More efficient?+joinE :: Ord t => Event' t (Event' t a) -> Event' t a+joinE = inE $ concatF . (fmap.fmap) unE++-- Derivation:+-- +-- Event (Event a) +-- --> [Fut (Event a)] -- unE+-- --> [Fut [Fut a]] -- (fmap.fmap) unE+-- --> [Fut a] -- concatF+-- --> Event a -- E++-- My previous attempt:++-- joinE :: Ord t => Event' t (Event' t a) -> Event' t a+-- joinE = mconcat . fmap (E . fmap join . sequenceF . fmap unE) . unE+--+-- Derivation:+-- +-- Event (Event a) +-- --> [Fut (Event a)] -- unE+-- --> [Fut [Fut a]] -- (fmap.fmap) unE+-- --> [[Fut (Fut a)]] -- fmap sequenceF+-- --> [[Fut a]] -- (fmap.fmap) join+-- --> [Event a] -- fmap E+-- --> Event a -- mconcat++-- I don't think joinE works as I want. The join on Fut makes sure that+-- the inner occurrences follow the outer, but I don't think fact is+-- visible to the implementation. Also, note that the mconcat could have+-- an infinite number of lists to merge.++flatFFs :: Ord t => Future t [Future t a] -> [Future t a]+flatFFs = fmap join . sequenceF++concatF :: Ord t => [Future t [Future t a]] -> [Future t a]+concatF = futVal . foldr mergeF (pure [])++-- Binary merge. The second argument is required to have the property+-- that sub-futures are all at least as late as the containing future.+-- The result is then guaranteed to have the same property, which allows+-- use of futVal instead of flatFFs in concatF.+mergeF :: Ord t => Binop (Future t [Future t a])+ffa `mergeF` Future (tb,futbs) =+ -- First the a values before tb, then interleave the rest of the a+ -- values with the b values. + Future (futTime ffa, prefa ++ (suffa `merge` futbs))+ where+ (prefa,suffa) = span ((<= tb).futTime) (flatFFs ffa)++-- TODO: try out a more efficient version of mergeF that doesn't use+-- (++). Idea: add a span to Data.DList and use it. Efficient &+-- elegant.+++-- | Accumulating event, starting from an initial value and a+-- update-function event. See also 'accumR'.+accumE :: Ord t => a -> Event' t (a -> a) -> Event' t a+accumE a = inE $ \ futfs -> accum (pure a) (fmap (<*>) futfs)+++{--------------------------------------------------------------------+ Misc utilities+--------------------------------------------------------------------}++-- | Merge two ordered lists into an ordered list.+merge :: Ord a => [a] -> [a] -> [a]+[] `merge` vs = vs+us `merge` [] = us+us@(u:us') `merge` vs@(v:vs') =+ if u <= v then+ u : (us' `merge` vs )+ else+ v : (us `merge` vs')+++accum :: a -> [a->a] -> [a]+accum _ [] = []+accum a (f:fs) = a' : accum a' fs where a' = f a++-- or+-- accum a = tail . scanl (flip ($)) a
src/Data/SFuture.hs view
@@ -50,14 +50,28 @@ -- Please keep in mind that this module specifies the interface and -- semantics, rather than a useful implementation. See "Data.Future" for -- an implementation that nearly implements the semantics described here.+-- +-- On second thought, I'm experimenting with using this module in an+-- usable implementation of events. See Data.MEvent. ---------------------------------------------------------------------- -module Data.SFuture where+module Data.SFuture + (+ -- * Time & futures+ Time, Future(..), futTime, futVal, sequenceF+ -- * To go elsewhere+ , Max(..), Min(..), AddBounds(..)+ ) where import Data.Monoid (Monoid(..)) import Control.Applicative (Applicative(..))--- import Data.Function (on)+import Data.Function (on) ++{----------------------------------------------------------+ Time and futures+----------------------------------------------------------}+ -- | Time of some event occurrence, which can be any @Ord@ type. In an -- actual implementation, we would not usually have access to the time -- value until (slightly after) that time. Extracting the actual time@@ -70,24 +84,62 @@ -- | A future value of type @a@ with time type @t@. Semantically, just a -- time\/value pair, but those values would not be available until -- 'force'd, which could block.-newtype Future t a = Future (Time t, a)+newtype Future t a = Future { unFuture :: (Time t, a) } deriving (Functor, Applicative, Monad, Show) -- The 'Applicative' instance relies on the 'Monoid' instance of 'Max'. --- | Force a future. The real version blocks until knowable.-force :: Future t a -> (Time t,a)-force (Future p) = p+-- | A future's time+futTime :: Future t a -> Time t+futTime = fst . unFuture --- The Monoid instance picks the earlier future+-- | A future's value+futVal :: Future t a -> a+futVal = snd . unFuture+++-- -- The Monoid instance picks the earlier future+-- instance Ord t => Monoid (Future t a) where+-- mempty = Future (maxBound, error "it'll never happen, buddy")+-- fut@(Future (t,_)) `mappend` fut'@(Future (t',_)) =+-- if t <= t' then fut else fut'++-- or:+++instance Eq (Future t a) where+ (==) = error "sorry, no (==) for futures"++instance Ord t => Ord (Future t a) where+ (<=) = (<=) `on` futTime+ -- We could leave 'min' to the default in terms of '(<=)', but the+ -- following can yield partial time info, as much as allowed by the time+ -- parameter type @t@ and its 'min'.+ Future (s,a) `min` Future (t,b) =+ Future (s `min` t, if s <= t then a else b)++-- For some choices of @t@, there may be an efficient combination of 'min'+-- and '(<=)'. In particular, 'Improving' has 'minI'.+ instance Ord t => Monoid (Future t a) where mempty = Future (maxBound, error "it'll never happen, buddy")- fut@(Future (t,_)) `mappend` fut'@(Future (t',_)) =- if t <= t' then fut else fut'+ mappend = min +-- 'sequenceF' is like 'sequenceA' from "Data.Traversable". However,+-- the @Traversable@ class assumes @Foldable@, which I'm not confident+-- how to implement usefully. (I could of course just strip off the+-- 'Future' constructor and the time. Why is Foldable required? --------- To go elsewhere+-- | Make a future container into a container of futures.+sequenceF :: Functor f => Future t (f a) -> f (Future t a)+sequenceF (Future (tt, f)) = fmap (Future . ((,) tt)) f +++{----------------------------------------------------------+ To go elsewhere+----------------------------------------------------------}+ -- For Data.Monoid: -- | Ordered monoid under 'max'.@@ -115,8 +167,8 @@ -- Equivalent to the Monad Writer instance. -- import Data.Monoid instance Monoid o => Monad ((,) o) where- return = pure- (o,a) >>= f = (o `mappend` o', a') where (o',a') = f a+ return = pure+ (o,a) >>= f = (o `mappend` o', a') where (o',a') = f a -- Alternatively, -- m >>= f = join (fmap f m)
+ src/Data/SImproving.hs view
@@ -0,0 +1,60 @@+{-# OPTIONS -Wall #-}+----------------------------------------------------------------------+-- |+-- Module : Data.Improving+-- Copyright : (c) Conal Elliott 2008+-- License : BSD3+-- +-- Maintainer : conal@conal.net+-- Stability : experimental+-- +-- \"Improving values\" from Warren Burton's \"Encapsulating Nondeterminacy+-- in an Abstract Data Type with Deterministic Semantics\".+-- +-- TODO: an efficient, referentially transparent, side-effecting version.+----------------------------------------------------------------------++module Data.Improving+ (+ Improving(..), exact+ -- , Improves, merge+ -- , Future(..)+ ) where++import Data.Function (on)++-- | Progressive information about a value (e.g., a time)+data Improving t = AtLeast t (Improving t) | Exactly t deriving Show++-- | Extract an exact value from an improving value+exact :: Improving t -> t+exact (AtLeast _ u) = exact u+exact (Exactly t) = t++instance Eq t => Eq (Improving t) where+ (==) = (==) `on` exact++instance Ord t => Ord (Improving t) where+ Exactly s `compare` Exactly t = s `compare` t+ AtLeast s u' `compare` v@(Exactly t) =+ if s > t then GT else u' `compare` v+ u@(Exactly s) `compare` AtLeast t v' =+ if s < t then LT else u `compare` v'+ u@(AtLeast s u') `compare` v@(AtLeast t v') =+ -- move forward where we know less+ if s <= t then+ u' `compare` v+ else+ u `compare` v'++ Exactly s `min` Exactly t = Exactly (s `min` t)+ AtLeast s u' `min` v@(Exactly t) =+ if s > t then v else u' `min` v+ u@(Exactly s) `min` AtLeast t v' =+ if s < t then u else u `min` v'+ u@(AtLeast s u') `min` v@(AtLeast t v') =+ -- move forward where we know less+ if s <= t then+ u' `min` v+ else+ u `min` v'
+ src/Data/SReactive.hs view
@@ -0,0 +1,169 @@+{-# OPTIONS_GHC -Wall #-}+{-# LANGUAGE TypeSynonymInstances, ScopedTypeVariables #-}+----------------------------------------------------------------------+-- |+-- Module : Data.SReactive+-- Copyright : (c) Conal Elliott 2008+-- License : BSD3+-- +-- Maintainer : conal@conal.net+-- Stability : experimental+-- +-- Simple, semantics-based reactive values+----------------------------------------------------------------------++module Data.SReactive+ (+ -- * Primitives+ Reactive'(..), stepper+ , joinR+ -- * Extras (defined via primitives)+ , Reactive+ , switcher, snapshot, snapshot_, whenE+ , accumR, scanlR, monoidR, maybeR, flipFlop, countR, traceR+ ) where++import Control.Applicative+import Control.Monad+import Data.Monoid++-- TypeCompose+import Control.Compose (Unop)+import Data.Pair (Pair(..),pairEdit)++import Data.EventExtras+import Data.Improving+++{----------------------------------------------------------+ Primitives+----------------------------------------------------------}++data Reactive' t a =+ Stepper {+ rInit :: a -- ^ initial value+ , rEvent :: Event' t a -- ^ waiting for event+ }++-- | Reactive value from an initial value and a new-value event.+stepper :: a -> Event' t a -> Reactive' t a+stepper = Stepper+++instance Ord t => Pair (Reactive' t) where+ -- pair :: Reactive' t a -> Reactive' t b -> Reactive' t (a,b)+ (c `Stepper` ce) `pair` (d `Stepper` de) =+ (c,d) `accumR` pairEdit (ce,de)++instance Functor (Reactive' t) where+ fmap f (a `Stepper` e) = f a `stepper` fmap f e++instance Ord t => Applicative (Reactive' t) where+ pure a = a `stepper` mempty+ -- Standard definition. See 'Pair'.+ rf <*> rx = uncurry ($) <$> (rf `pair` rx)++-- A wonderful thing about the <*> definition for Reactive' t is that it+-- automatically caches the previous value of the function or argument+-- when the argument or function changes.++instance Ord t => Monad (Reactive' t) where+ return = pure+ r >>= f = joinR (f <$> r)++-- | Reactive' t 'join' (equivalent to 'join' but slightly more efficient, I think)+joinR :: Ord t => Reactive' t (Reactive' t a) -> Reactive' t a+joinR ((a `Stepper` e) `Stepper` er) = + a `stepper` (e `mappend` join (rToE <$> er))++-- | Turn a reactive value into an event, given a time for the initial+-- occurrence.+rToE :: Ord t => Reactive' t a -> Event' t a+rToE (a `Stepper` e) = pure a `mappend` e++-- e :: Event' t a+-- er :: Event' t (Reactive' t a)+-- +-- rToE <$> er ::: Event' t (Event' t a)+-- join (rToE <$> er) ::: Event' t a++-- This variant of 'snapshot' has 'Nothing's where @b@ changed and @a@+-- didn't.+snap :: forall a b t. Ord t =>+ Event' t a -> Reactive' t b -> Event' 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)+++{----------------------------------------------------------+ Extras (defined via primitives)+----------------------------------------------------------}++type Reactive = Reactive' (Improving Double)++-- | Snapshot a reactive value whenever an event occurs.+snapshot :: Ord t => Event' t a -> Reactive' t b -> Event' t (a,b)+e `snapshot` r = joinMaybes $ fmap f (e `snap` r)+ where+ f (Nothing,_) = Nothing+ f (Just a ,b) = Just (a,b)++-- | Switch between reactive values.+switcher :: Ord t => Reactive' t a -> Event' t (Reactive' t a) -> Reactive' t a+r `switcher` e = joinR (r `stepper` e)++-- | Like 'snapshot' but discarding event data (often @a@ is @()@).+snapshot_ :: Ord t => Event' t a -> Reactive' t b -> Event' t b+e `snapshot_` src = snd <$> (e `snapshot` src)++-- | Filter an event according to whether a boolean source is true.+whenE :: Ord t => Event' t a -> Reactive' t Bool -> Event' t a+whenE e = joinMaybes . fmap h . snapshot e+ where+ h (a,True) = Just a+ h (_,False) = Nothing+++-- | Reactive' t value from an initial value and an updater event. See also+-- 'accumE'.+accumR :: Ord t => a -> Event' t (a -> a) -> Reactive' t a+a `accumR` e = a `stepper` (a `accumE` e)++-- | Like 'scanl' for reactive values. See also 'scanlE'.+scanlR :: Ord t => (a -> b -> a) -> a -> Event' t b -> Reactive' t a+scanlR f a e = a `stepper` scanlE f a e++-- | Accumulate values from a monoid-valued event. Specialization of+-- 'scanlE', using 'mappend' and 'mempty'. See also 'monoidE'.+monoidR :: (Ord t, Monoid a) => Event' t a -> Reactive' t a+monoidR = scanlR mappend mempty++-- | Start out blank ('Nothing'), latching onto each new @a@, and blanking+-- on each @b@. If you just want to latch and not blank, then use+-- 'mempty' for @lose@.+maybeR :: Ord t => Event' t a -> Event' t b -> Reactive' t (Maybe a)+maybeR get lose =+ Nothing `stepper` (fmap Just get `mappend` (Nothing <$ lose))++-- | Flip-flopping source. Turns true when @ea@ occurs and false when+-- @eb@ occurs.+flipFlop :: Ord t => Event' t a -> Event' t b -> Reactive' t Bool+flipFlop ea eb =+ False `stepper` ((True <$ ea) `mappend` (False <$ eb))++-- TODO: generalize 'maybeR' & 'flipFlop'. Perhaps using 'Monoid'.+-- Note that Nothing and (Any False) are mempty.++-- | Count occurrences of an event. See also 'countE'.+countR :: (Ord t, Num n) => Event' t a -> Reactive' t n+countR e = 0 `stepper` countE_ e++-- | Tracing of reactive values+traceR :: (a -> String) -> Unop (Reactive' t a)+traceR shw (a `Stepper` e) = a `Stepper` traceE shw e+
+ src/Data/Unamb.hs view
@@ -0,0 +1,83 @@+{-# LANGUAGE RecursiveDo #-}+----------------------------------------------------------------------+-- |+-- Module : Data.Unamb+-- Copyright : (c) Conal Elliott 2008+-- License : BSD3+-- +-- Maintainer : conal@conal.net+-- Stability : experimental+-- +-- Unambiguous choice+----------------------------------------------------------------------++module Data.Unamb+ (+ unamb, amb, race, assuming+ ) where++-- For hang+import Control.Monad (forever)+import System.IO.Unsafe++-- For unamb+import Control.Concurrent+import Control.Exception (evaluate)+++-- | Unambiguous choice operator. Equivalent to the ambiguous choice+-- operator, but with arguments restricted to be equal where not bottom,+-- so that the choice doesn't matter. See also 'amb'.+unamb :: a -> a -> a+a `unamb` b = unsafePerformIO (a `amb` b)+++-- | Ambiguous choice operator. Yield either value. Evaluates in+-- separate threads and picks whichever finishes first. See also+-- 'unamb' and 'race'.+amb :: a -> a -> IO a+a `amb` b = evaluate a `race` evaluate b++-- | Race two actions against each other in separate threads, and pick+-- whichever finishes first. See also 'amb'.+race :: IO a -> IO a -> IO a+a `race` b = + -- Evaluate a and b in concurrent threads. Whichever thread finishes+ -- first kill the other thread.+ do v <- newEmptyMVar -- to hold a or b+ lock <- newEmptyMVar -- to avoid double-kill+ -- Evaluate one value and kill the other.+ let run io tid = forkIO $ do x <- io+ putMVar lock ()+ killThread tid+ putMVar v x+ mdo ta <- run a tb+ tb <- run b ta+ return ()+ readMVar v++-- Without using unsafePerformIO, is there a way to define a+-- non-terminating but non-erroring pure value that consume very little+-- resources while not terminating?++-- | Never yield an answer. Like 'undefined' or 'error "whatever"', but+-- don't raise an error, and don't consume computational resources.+hang :: a+hang = unsafePerformIO hangIO++-- | Block forever+hangIO :: IO a+hangIO = do -- putStrLn "warning: blocking forever."+ -- Any never-terminating computation goes here+ -- This one can yield an exception "thread blocked indefinitely"+ -- newEmptyMVar >>= takeMVar+ -- sjanssen suggests this alternative:+ forever $ threadDelay maxBound+ -- forever's return type is (), though it could be fully+ -- polymorphic. Until it's fixed, I need the following line.+ return undefined+++-- | Yield a value if a condition is true. Otherwise wait forever.+assuming :: Bool -> a -> a+assuming c a = if c then a else hang
src/Examples.hs view
@@ -17,7 +17,7 @@ -- base import Data.Monoid import Data.IORef-import Control.Monad ((>=>),forM_)+import Control.Monad import Control.Applicative import Control.Arrow (first,second) import Control.Concurrent (yield, forkIO, killThread, threadDelay, ThreadId)@@ -242,3 +242,70 @@ -- two different previous GUI examples. main = t6+++updPair :: Either c d -> (c,d) -> (c,d)+updPair = (first.const) `either` (second.const)++-- updPair (Left c') (_,d) = (c',d)+-- updPair (Right d') (c,_) = (c,d')++-- mixEither :: (Event c, Event d) -> Event (Either c d)+-- mixEither :: (Functor f, Monoid (f (Either a b))) =>+-- (f a, f b) -> f (Either a b)+mixEither :: MonadPlus m => (m a, m b) -> m (Either a b)+mixEither (ec,ed) = liftM Left ec `mplus` liftM Right ed++-- unmixEither :: Event (Either c d) -> (Event c, Event d)+unmixEither :: MonadPlus m => m (Either c d) -> (m c, m d)+unmixEither ecd = (filt left, filt right)+ where+ filt f = joinMaybes (liftM f ecd)++left :: Either c d -> Maybe c+left (Left c) = Just c+left _ = Nothing++right :: Either c d -> Maybe d+right (Right d) = Just d+right _ = Nothing+++-- pairEditE :: (Event c, Event d) -> Event ((c,d) -> (c,d))++-- pairEditE :: (Functor f, Monoid (f ((d, a) -> (d, a)))) =>+-- (f d, f a) -> f ((d, a) -> (d, a))+-- pairEditE (ce,de) =+-- ((first.const) <$> ce) `mappend` ((second.const) <$> de)++-- pairEditE :: (Functor m, MonadPlus m) => (m d, m a) -> m ((d, a) -> (d, a))+-- pairEditE (ce,de) =+-- ((first.const) <$> ce) `mplus` ((second.const) <$> de)++pairEditE :: MonadPlus m => (m c,m d) -> m ((c,d) -> (c,d))+pairEditE = liftM updPair . mixEither++-- pairEditE cde = liftM updPair (mixEither cde)++-- or, skipping sums++-- pairEditE (ce,de) =+-- liftM (first.const) ce `mplus` liftM (second.const) de++pairE :: (c,d) -> (Event c, Event d) -> Event (c,d)+pairE cd cde = cd `accumE` pairEditE cde++pairR :: Reactive c -> Reactive d -> Reactive (c,d)++-- (c `Stepper` ce) `pairR` (d `Stepper` de) =+-- (c,d) `stepper` pairE (c,d) (ce,de)++-- More directly:++(c `Stepper` ce) `pairR` (d `Stepper` de) =+ (c,d) `accumR` pairEditE (ce,de)++-- pairR' :: Reactive c -> Reactive d -> Reactive (c,d)+-- (c `Stepper` ce) `pairR'` (d `Stepper` de) =+-- (c,d) `accumR` pairEditE (ce,de)+