reactive 0.5 → 0.5.0.1
raw patch · 15 files changed
+14/−1104 lines, 15 filesdep ~TypeComposedep ~basenew-uploader
Dependency ranges changed: TypeCompose, base
Files
- Makefile +0/−3
- README +0/−31
- changes.tw +0/−37
- reactive.cabal +2/−2
- src/Data/EventExtras.hs +0/−107
- src/Data/Fun.hs +8/−4
- src/Data/Future.hs +2/−2
- src/Data/Improving.hs +0/−53
- src/Data/MEvent.hs +0/−72
- src/Data/Reactive.hs +2/−2
- src/Data/SEvent.hs +0/−168
- src/Data/SImproving.hs +0/−60
- src/Data/SReactive.hs +0/−169
- src/Data/Unamb.hs +0/−83
- src/Examples.hs +0/−311
− Makefile
@@ -1,3 +0,0 @@-# For special configuration, especially for docs. Otherwise see README.--include ../my-cabal-make.inc
− README
@@ -1,31 +0,0 @@-_Reactive_ [1] is a simple foundation for programming reactive systems-functionally. Like Fran/FRP, it has a notions of (reactive) behaviors and-events. Like DataDriven [2], Reactive has a data-driven implementation.-The main difference between Reactive and DataDriven is that Reactive-builds on MVar-based "futures", while DataDriven builds on-continuation-based computations.--The inspiration for Reactive was Mike Sperber's Lula [3] implementation of-FRP. Mike used blocking threads, which I had never considered for FRP.-While playing with the idea, I realized that I could give a very elegant-and efficient solution to caching, which DataDriven doesn't do. (For an-application "f <*> a" of a varying function to a varying argument, caching-remembers the latest function to apply to a new argument and the last-argument to which to apply a new function.)--Please share any comments & suggestions on the discussion (talk) page-there.--You can configure, build, and install all in the usual way with Cabal-commands.-- runhaskell Setup.lhs configure- runhaskell Setup.lhs build- runhaskell Setup.lhs install---References:--[1] http://haskell.org/haskellwiki/Reactive-[2] http://haskell.org/haskellwiki/DataDriven-[3] http://www-pu.informatik.uni-tuebingen.de/lula/deutsch/publications.html
− changes.tw
@@ -1,37 +0,0 @@-== 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.--=== Version 0.2 ===--* Fixed <hask>switcher</hask>. Didn't terminate. Thanks to Ivan Tomac for the bug report.--=== Version 0.1 ===--* Added <hask>Never</hask> constructor for Future. Allows optimizations, including a huge improvement for <hask>(>>=)</hask> on <hask>Event</hask> (which had been piling up <hask>never</hask>s).-* removed <code>-threaded</code> comment-* added <hask>traceR</hask> (reactive value tracing)-* use idler in <code>src/Examples.hs</code> (for single-threaded use of wxHaskell)--=== Version 0.0 ===--* New project.
reactive.cabal view
@@ -1,5 +1,5 @@ Name: reactive-Version: 0.5+Version: 0.5.0.1 Synopsis: Simple foundation for functional reactive programming Category: reactivity, FRP Description:@@ -26,7 +26,7 @@ build-type: Simple Hs-Source-Dirs: src Extensions: -Build-Depends: base, TypeCompose>=0.3+Build-Depends: base >= 3.0.3.2 && < 5, TypeCompose>=0.6.7 Exposed-Modules: Data.SFuture Data.Future
− src/Data/EventExtras.hs
@@ -1,107 +0,0 @@-{-# 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
@@ -15,7 +15,8 @@ import Data.Monoid (Monoid(..)) import Control.Applicative (Applicative(..))-import Control.Arrow hiding (pure)+import qualified Control.Category (Category, (.), id)+import Control.Arrow (Arrow, arr, first, second, (***), (>>>)) -- | Constant-optimized functions data Fun t a = K a -- ^ constant function@@ -47,11 +48,14 @@ K a >>= h = h a Fun f >>= h = Fun (f >>= apply . h) +instance Control.Category.Category Fun where+ id = arr id+ K b . _ = K b+ Fun g . K a = K (g a)+ Fun f . Fun g = Fun (f . g)+ instance Arrow Fun where arr = Fun- _ >>> K b = K b- K a >>> Fun g = K (g a)- Fun g >>> Fun f = Fun (g >>> f) first = Fun . first . apply second = Fun . second . apply K a' *** K b' = K (a',b')
src/Data/Future.hs view
@@ -1,6 +1,6 @@--- {-# LANGUAGE RecursiveDo #-}+{-# LANGUAGE RecursiveDo #-} -- For ghc-6.6 compatibility-{-# OPTIONS_GHC -fglasgow-exts #-}+-- {-# OPTIONS_GHC -fglasgow-exts #-} ---------------------------------------------------------------------- -- |
− src/Data/Improving.hs
@@ -1,53 +0,0 @@-------------------------------------------------------------------------- |--- 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
@@ -1,72 +0,0 @@-{-# 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
@@ -434,7 +434,7 @@ { mempty_f = O (pure mempty_f); mappend_f = inO2 (liftA2 mappend_f) } instance Pair f => Pair (Reactive :. f) where pair = apPair -instance Unpair Reactive where {pfst = fmap fst; psnd = fmap snd}+instance Unpair Reactive where {fsts = fmap fst; snds = fmap snd} -- Standard instances instance Monoid_f Event where@@ -447,7 +447,7 @@ pair = copair -- Standard instance for functors-instance Unpair Event where {pfst = fmap fst; psnd = fmap snd}+instance Unpair Event where {fsts = fmap fst; snds = fmap snd}
− src/Data/SEvent.hs
@@ -1,168 +0,0 @@-{-# 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/SImproving.hs
@@ -1,60 +0,0 @@-{-# 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
@@ -1,169 +0,0 @@-{-# 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
@@ -1,83 +0,0 @@-{-# 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
@@ -1,311 +0,0 @@-{-# LANGUAGE TypeOperators, FlexibleContexts, TypeSynonymInstances, FlexibleInstances #-}--------------------------------------------------------------------------- |--- Module : Examples--- Copyright : (c) Conal Elliott 2007--- License : BSD3--- --- Maintainer : conal@conal.net--- Stability : experimental--- --- Simple test for Reactive--------------------------------------------------------------------------- module Main where---- base-import Data.Monoid-import Data.IORef-import Control.Monad-import Control.Applicative-import Control.Arrow (first,second)-import Control.Concurrent (yield, forkIO, killThread, threadDelay, ThreadId)---- wxHaskell-import Graphics.UI.WX hiding (Event,Reactive)-import qualified Graphics.UI.WX as WX--- TypeCompose-import Control.Compose ((:.)(..), inO,inO2)-import Data.Title---- Reactive-import Data.Reactive---{--------------------------------------------------------------------- Mini-Phooey---------------------------------------------------------------------}--type Win = Panel ()--type Wio = ((->) Win) :. IO :. (,) Layout--type Wio' a = Win -> IO (Layout,a)---wio :: Wio' a -> Wio a-wio = O . O--unWio :: Wio a -> Wio' a-unWio = unO . unO--inWio :: (Wio' a -> Wio' b) -> (Wio a -> Wio b)-inWio f = wio . f . unWio--inWio2 :: (Wio' a -> Wio' b -> Wio' c) -> (Wio a -> Wio b -> Wio c)-inWio2 f = inWio . f . unWio--instance Title_f Wio where- title_f str = inWio ((fmap.fmap.first) (boxed str))---- Bake in vertical layout. See phooey for flexible layout.-instance Monoid Layout where- mempty = WX.empty- mappend = above--instance Monoid a => Monoid (Wio a) where- mempty = wio mempty- mappend = inWio2 mappend--type WioE a = Wio (Event a)-type WioR a = Wio (Reactive a)--buttonE :: String -> WioE ()-buttonE str = wio $ \ win ->- do (e, snk) <- mkEvent- b <- button win [ text := str, on command := snk () ]- return (hwidget b, e)--buttonE' :: String -> a -> WioE a-buttonE' str a = (a `replace`) <$> buttonE str--sliderE :: (Int,Int) -> Int -> WioE Int-sliderE (lo,hi) initial = wio $ \ win ->- do (e, snk) <- mkEvent- s <- hslider win True lo hi- [ selection := initial ]- set s [ on command := getAttr selection s >>= snk ]- return (hwidget s, e)--sliderR :: (Int,Int) -> Int -> WioR Int-sliderR lh initial = stepper initial <$> sliderE lh initial--stringO :: Wio (Sink String)-stringO = attrO (flip textEntry []) text---- Make an output. The returned sink collects updates. On idle, the--- latest update gets stored in the given attribute.-attrO :: Widget w => (Win -> IO w) -> Attr w a -> Wio (Sink a)-attrO mk attr = wio $ \ win ->- do ctl <- mk win- ref <- newIORef Nothing- setAttr (on idle) win $- do readIORef ref >>= maybe mempty (setAttr attr ctl)- writeIORef ref Nothing- return True- return (hwidget ctl , writeIORef ref . Just)---- -- The following alternative ought to be more efficient. Oddly, the timer--- -- doesn't get restarted, although enabled gets set to True.---- stringO = wio $ \ win ->--- do ctl <- textEntry win []--- ref <- newIORef (error "stringO: no initial value")--- tim <- timer win [ interval := 10, enabled := False ]--- let enable b = do putStrLn $ "enable: " ++ show b--- setAttr enabled tim b--- set tim [ on command := do putStrLn "timer"--- readIORef ref >>= setAttr text ctl--- enable False--- ]--- return ( hwidget ctl--- , \ str -> writeIORef ref str >> enable True )--showO :: Show a => Wio (Sink a)-showO = (. show) <$> stringO--showR :: Show a => WioR (Sink a)-showR = pure <$> showO----- | Horizontally-filled widget layout-hwidget :: Widget w => w -> Layout-hwidget = hfill . widget---- | Binary layout combinator-above, leftOf :: Layout -> Layout -> Layout-la `above` lb = fill (column 0 [la,lb])-la `leftOf` lb = fill (row 0 [la,lb])---- | Get attribute. Just a flipped 'get'. Handy for partial application.-getAttr :: Attr w a -> w -> IO a-getAttr = flip get---- | Set a single attribute. Handy for partial application.-setAttr :: Attr w a -> w -> Sink a-setAttr attr ctl x = set ctl [ attr := x ]---{--------------------------------------------------------------------- Running---------------------------------------------------------------------}---- | Fork a 'Wio': handle frame & widget creation, and apply layout.-forkWio :: (o -> IO ThreadId) -> String -> Wio o -> IO ()-forkWio forker name w = start $- do f <- frame [ visible := False, text := name ]- pan <- panel f []- (l,o) <- unWio w pan- set pan [ layout := l ]- forker o- -- Yield regularly, to allow other threads to continue. Unnecessary- -- when apps are compiled with -threaded.- -- timer pan [interval := 10, on command := yield]- set f [ layout := fill (widget pan)- , visible := True- ]---- | Fork a 'WioE'-forkWioE :: String -> WioE Action -> IO ()-forkWioE = forkWio forkE---- | Fork a 'WioR'-forkWioR :: String -> WioR Action -> IO ()-forkWioR = forkWio forkR---{--------------------------------------------------------------------- Examples---------------------------------------------------------------------}--alarm :: Double -> Int -> IO (Event Int)-alarm secs reps =- do (e,snk) <- mkEvent- forkIO $ forM_ [1 .. reps] $ \ i ->- do threadDelay micros- snk i- return e- where- micros = round (1.0e6 * secs)- --t0 = alarm 0.5 10 >>= \ e -> runE $ print <$> {-traceE (const "boo!")-} e--mkAB :: WioE String-mkAB = buttonE' "a" "a" `mappend` buttonE' "b" "b"---t1 = forkWioE "t1" $ liftA2 (<$>) stringO mkAB--acc :: WioE String-acc = g <$> mkAB- where- g :: Event String -> Event String- g e = "" `accumE` (flip (++) <$> e)--t2 = forkWioE "t2" $ liftA2 (<$>) stringO acc--total :: Show a => WioR (Sink a)-total = title "total" showR--sl :: Int -> WioR Int-sl = sliderR (0,100)--apples, bananas, fruit :: WioR Int-apples = title "apples" $ sl 3-bananas = title "bananas" $ sl 7-fruit = title "fruit" $ (liftA2.liftA2) (+) apples bananas--t3 = forkWioR "t3" $ liftA2 (<**>) fruit total --t4 = forkWioR "t4" $ liftA2 (<*>) showR (sl 0)--t5 = forkWioR "t5" $ liftA2 (<$>) showO (sl 0)---- This example shows what happens with expensive computations. There's a--- lag between slider movement and shown result. Can even get more than--- one computation behind.-t6 = forkWioR "t6" $ liftA2 (<$>) showO (fmap (ack 2) <$> sliderR (0,1000) 0)--ack 0 n = n+1-ack m 0 = ack (m-1) 1-ack m n = ack (m-1) (ack m (n-1))---- Test switchers. Ivan Tomac's example.-sw1 = do (e, snk) <- mkEvent- forkR $ print <$> pure "init" `switcher` ((\_ -> pure "next") <$> e)- snk ()- snk ()---- TODO: replace sw1 with a declarative GUI example, say switching between--- 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)-