packages feed

reactive-banana 0.5.0.3 → 0.6.0.0

raw patch · 10 files changed

+473/−283 lines, 10 filesdep ~containersdep ~unordered-containersPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: containers, unordered-containers

API changes (from Hackage documentation)

- Reactive.Banana.Combinators: instance Monoid (Event t (a -> a))
- Reactive.Banana.Frameworks: instance Typeable EventNetwork
- Reactive.Banana.Frameworks: interpret :: (forall t. Event t a -> Event t b) -> [a] -> IO [[b]]
- Reactive.Banana.Internal.Model: interpretModel :: (Event Expr a -> Event Expr b) -> Event a -> IO (Event b)
+ Reactive.Banana.Combinators: type PrimBehavior = Behavior Expr
+ Reactive.Banana.Combinators: type PrimEvent = Event Expr
+ Reactive.Banana.Combinators: unions :: [Event t a] -> Event t a
+ Reactive.Banana.Frameworks: interpretFrameworks :: (forall t. Event t a -> Event t b) -> [a] -> IO [[b]]
+ Reactive.Banana.Model: StepperB :: a -> (Event a) -> Behavior a
+ Reactive.Banana.Model: accumE :: a -> Event (a -> a) -> Event a
+ Reactive.Banana.Model: applyB :: Behavior (a -> b) -> Behavior a -> Behavior b
+ Reactive.Banana.Model: applyE :: Behavior (a -> b) -> Event a -> Event b
+ Reactive.Banana.Model: data Behavior a
+ Reactive.Banana.Model: filterE :: (a -> Bool) -> Event a -> Event a
+ Reactive.Banana.Model: interpretModel :: (Event a -> Event b) -> Event a -> IO (Event b)
+ Reactive.Banana.Model: mapB :: (a -> b) -> Behavior a -> Behavior b
+ Reactive.Banana.Model: mapE :: (a -> b) -> Event a -> Event b
+ Reactive.Banana.Model: never :: Event a
+ Reactive.Banana.Model: pureB :: a -> Behavior a
+ Reactive.Banana.Model: stepperB :: a -> [Maybe a] -> Behavior a
+ Reactive.Banana.Model: type Event a = [Maybe a]
+ Reactive.Banana.Model: unionWith :: (a -> a -> a) -> Event a -> Event a -> Event a
- Reactive.Banana.Combinators: B :: Behavior Expr a -> Behavior t a
+ Reactive.Banana.Combinators: B :: PrimBehavior a -> Behavior t a
- Reactive.Banana.Combinators: E :: Event Expr [a] -> Event t a
+ Reactive.Banana.Combinators: E :: PrimEvent [a] -> Event t a
- Reactive.Banana.Combinators: unB :: Behavior t a -> Behavior Expr a
+ Reactive.Banana.Combinators: unB :: Behavior t a -> PrimBehavior a
- Reactive.Banana.Combinators: unE :: Event t a -> Event Expr [a]
+ Reactive.Banana.Combinators: unE :: Event t a -> PrimEvent [a]

Files

reactive-banana.cabal view
@@ -1,5 +1,5 @@ Name:                reactive-banana-Version:             0.5.0.3+Version:             0.6.0.0 Synopsis:            Practical library for functional reactive programming (FRP). Description:              Reactive-banana is a practical library for Functional Reactive Programming (FRP).@@ -22,41 +22,63 @@ Stability:           Experimental Category:            FRP Cabal-version:       >=1.6-- Build-type:          Simple+ extra-source-files:  doc/examples/*.hs +Source-repository head+    type:               git+    location:           git://github.com/HeinrichApfelmus/reactive-banana.git+    subdir:             reactive-banana/++flag UseExtensions+    description: Use language extensions like type families or GADTs.+                 This enables the efficient push-driven implementation,+                 but doesn't necessarily work with compilers other than GHC.+-- Cabal checks if the package can be build with  UseExtensions = True,+-- otherewise it is set to  False .+ Library     hs-source-dirs:     src-    extensions:-                        TypeFamilies, FlexibleContexts,-                        FlexibleInstances, EmptyDataDecls,-                        GADTs, BangPatterns, TupleSections,-                        Rank2Types, NoMonomorphismRestriction,-                        DeriveDataTypeable-    build-depends:-        base >= 4.2 && < 5, containers >= 0.3 && < 0.5,-        transformers >= 0.2 && < 0.4,-        QuickCheck >= 1.2 && < 2.5,-        vault == 0.2.*, unordered-containers == 0.2.*, hashable == 1.1.*,-        fclabels == 1.1.*+    +    extensions:         CPP,+                        Rank2Types, NoMonomorphismRestriction, FlexibleInstances+    +    build-depends:      base >= 4.2 && < 5, containers >= 0.3 && < 0.6,+                        transformers >= 0.2 && < 0.4,+                        vault == 0.2.*+    +    if flag(UseExtensions)+        extensions:     TypeFamilies, GADTs, MultiParamTypeClasses,+                        BangPatterns, TupleSections,+                        EmptyDataDecls+        build-depends:  QuickCheck >= 1.2 && < 2.5,+                        fclabels == 1.1.*,+                        unordered-containers >= 0.2.1.0 && < 0.3,+                        hashable == 1.1.*+        CPP-options:    -DUseExtensions+             exposed-modules:                         Reactive.Banana,                         Reactive.Banana.Combinators,-                        Reactive.Banana.Frameworks,                         Reactive.Banana.Experimental.Calm,-                        Reactive.Banana.Internal.Model+                        Reactive.Banana.Frameworks,+                        Reactive.Banana.Model+         other-modules:-                        Reactive.Banana.Internal.AST,                         Reactive.Banana.Internal.InputOutput,-                        Reactive.Banana.Internal.PushGraph,-                        Reactive.Banana.Internal.TotalOrder,                         Reactive.Banana.Tests+    +    if flag(UseExtensions)+        other-modules:+                        Reactive.Banana.Internal.AST,+                        Reactive.Banana.Internal.InterpretModel,+                        Reactive.Banana.Internal.PushGraph,+                        Reactive.Banana.Internal.TotalOrder+    else+        other-modules:+                        Reactive.Banana.Internal.CompileModel +                         -Source-repository head-    type:               git-    location:           git://github.com/HeinrichApfelmus/reactive-banana.git-    subdir:             reactive-banana/ 
src/Reactive/Banana/Combinators.hs view
@@ -1,7 +1,10 @@ {------------------------------------------------------------------------------    Reactive Banana+    reactive-banana ------------------------------------------------------------------------------}-{-# LANGUAGE Rank2Types, MultiParamTypeClasses, TupleSections, FlexibleInstances #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE Rank2Types #-}+{-# LANGUAGE MultiParamTypeClasses #-}+-- #define UseExtensions 1  module Reactive.Banana.Combinators (     -- * Synopsis@@ -16,7 +19,7 @@     -- * Core Combinators     module Control.Applicative,     module Data.Monoid,-    never, union, filterE, collect, spill, accumE,+    never, union, unions, filterE, collect, spill, accumE,     apply, stepper,     -- $classes     @@ -30,6 +33,9 @@     calm, unionWith,     -- ** Apply class     Apply(..),+    +    -- * Internal+    PrimEvent, PrimBehavior,     ) where  import Control.Applicative@@ -38,11 +44,30 @@ import Data.Maybe (isJust) import Data.Monoid (Monoid(..)) +-- The efficient push-based implementation makes essential+-- use of several language extensions. To enable building+-- if other compilers, we can select the model implementation instead.+#if UseExtensions+ import Reactive.Banana.Internal.InputOutput-import qualified Reactive.Banana.Internal.AST as AST-import qualified Reactive.Banana.Internal.Model as Model import Reactive.Banana.Internal.PushGraph+import qualified Reactive.Banana.Internal.AST as Prim+import qualified Reactive.Banana.Internal.InterpretModel as Prim +type PrimEvent     = Prim.Event Prim.Expr+type PrimBehavior  = Prim.Behavior Prim.Expr++#else++import qualified Reactive.Banana.Model as Prim++type PrimEvent    a = Prim.Event a+type PrimBehavior a = Prim.Behavior a++#endif+++ {-----------------------------------------------------------------------------     Introduction ------------------------------------------------------------------------------}@@ -59,14 +84,14 @@  > type Event t a = [(Time,a)] -}-newtype Event t a = E { unE :: AST.Event AST.Expr [a] }+newtype Event t a = E { unE :: PrimEvent [a] }     -- ^ (Constructor exported for internal use only.)  {-| @Behavior t a@ represents a value that varies in time. Think of it as  > type Behavior t a = Time -> a -}-newtype Behavior t a = B { unB :: AST.Behavior AST.Expr a }+newtype Behavior t a = B { unB :: PrimBehavior a }     -- ^ (Constructor exported for internal use only.)  {-$intro2@@ -80,7 +105,7 @@ While the type synonyms mentioned above are the way you should think about 'Behavior' and 'Event', they are a bit vague for formal manipulation. To remedy this, the library provides a very simple but authoritative-model implementation. See 'Reactive.Banana.Model' for more.+model implementation. See "Reactive.Banana.Model" for more.  -} @@ -90,25 +115,31 @@ -- | Interpret with model implementation. -- Useful for testing. interpretModel :: (forall t. Event t a -> Event t b) -> [[a]] -> IO [[b]]-interpretModel f xs =-    map toList <$> Model.interpretModel (unE . f . E) (map Just xs)+interpretModel f xs = map toList <$> Prim.interpretModel (unE . f . E) (map Just xs) --- | Interpret with push-based implementation.+-- | Interpret with push-based implementation (if available for your compiler). -- Useful for testing. interpretPushGraph :: (forall t. Event t a -> Event t b) -> [[a]] -> IO [[b]]++#if UseExtensions+ interpretPushGraph f xs = do     i <- newInputChannel-    automaton <- compileToAutomaton (unE . f . E $ AST.inputE i)+    automaton <- compileToAutomaton (unE . f . E $ Prim.inputE i)     map toList <$> unfoldAutomaton automaton i xs +#else++interpretPushGraph = interpretModel++#endif+ toList :: Maybe [a] -> [a] toList Nothing   = [] toList (Just xs) = xs  {------------------------------------------------------------------------------    Basic combinators--    Implemented in terms of Reactive.Banana.Internal.AST+    Core combinators ------------------------------------------------------------------------------} singleton :: a -> [a] singleton x = [x]@@ -116,7 +147,7 @@ -- | Event that never occurs. -- Think of it as @never = []@. never    :: Event t a-never = E $ singleton <$> AST.never+never = E $ Prim.mapE singleton Prim.never  -- | Merge two event streams of the same type. -- In case of simultaneous occurrences, the left argument comes first.@@ -126,14 +157,20 @@ -- >    | timex <= timey = (timex,x) : union xs ((timey,y):ys) -- >    | timex >  timey = (timey,y) : union ((timex,x):xs) ys union    :: Event t a -> Event t a -> Event t a-union e1 e2 = E $ AST.unionWith (++) (unE e1) (unE e2)+union e1 e2 = E $ Prim.unionWith (++) (unE e1) (unE e2) +-- | Merge several event streams of the same type.+-- +-- > unions = foldr union never+unions :: [Event t a] -> Event t a+unions = foldr union never+ -- | Allow all events that fulfill the predicate, discard the rest. -- Think of it as --  -- > filterE p es = [(time,a) | (time,a) <- es, p a] filterE   :: (a -> Bool) -> Event t a -> Event t a-filterE p = E . AST.filterE (not . null) . (filter p <$>) . unE+filterE p = E . Prim.filterE (not . null) . (Prim.mapE (filter p)) . unE  -- | Collect simultaneous event occurences. -- The result will never contain an empty list.@@ -141,7 +178,7 @@ -- -- > collect [(time1, e1), (time1, e2)] = [(time1, [e1,e2])] collect   :: Event t a -> Event t [a]-collect e = E $ singleton <$> unE e+collect e = E $ Prim.mapE singleton (unE e)  -- | Emit simultaneous event occurrences. -- The first element in the list will be emitted first, and so on.@@ -150,7 +187,7 @@ -- -- > spill . collect = id spill :: Event t [a] -> Event t a-spill e = E $ concat <$> unE e+spill e = E $ Prim.mapE concat (unE e)  -- | Construct a time-varying function from an initial value and  -- a stream of new values. Think of it as@@ -164,7 +201,7 @@ -- Also note that in the case of simultaneous occurrences, -- only the last one is kept. stepper :: a -> Event t a -> Behavior t a-stepper x e = B $ AST.stepperB x (last <$> unE e)+stepper x e = B $ Prim.stepperB x $ Prim.mapE last $ unE e  -- | The 'accumE' function accumulates a stream of events. -- Example:@@ -175,14 +212,15 @@ -- Note that the output events are simultaneous with the input events, -- there is no \"delay\" like in the case of 'accumB'. accumE   :: a -> Event t (a -> a) -> Event t a-accumE acc = E . mapAccumE acc . fmap concatenate . unE+accumE acc = E . mapAccumE acc . Prim.mapE concatenate . unE     where     concatenate :: [a -> a] -> a -> ([a],a)     concatenate fs acc = (tail values, last values)         where values = scanl' (flip ($)) acc fs -    mapAccumE :: s -> AST.Event AST.Expr (s -> (a,s)) -> AST.Event AST.Expr a-    mapAccumE acc = fmap fst . AST.accumE (undefined,acc) . fmap (. snd)+    mapAccumE :: s -> PrimEvent (s -> (a,s)) -> PrimEvent a+    mapAccumE acc =+        Prim.mapE fst . Prim.accumE (undefined,acc) . Prim.mapE (. snd)  -- strict version of scanl scanl' :: (a -> b -> a) -> a -> [b] -> [a]@@ -195,7 +233,7 @@ --  -- > apply bf ex = [(time, bf time x) | (time, x) <- ex] apply    :: Behavior t (a -> b) -> Event t a -> Event t b-apply bf ex = E $ AST.applyE (map <$> unB bf) (unE ex)+apply bf ex = E $ Prim.applyE (Prim.mapB map $ unB bf) (unE ex)  {-$classes @@ -224,16 +262,19 @@  -} +{- No monoid instance, sorry.+ instance Monoid (Event t (a -> a)) where     mempty  = never     mappend = unionWith (flip (.))+-}  instance Functor (Event t) where-    fmap f e = E $ (map f) <$> (unE e)+    fmap f e = E $ Prim.mapE (map f) (unE e)  instance Applicative (Behavior t) where-    pure x = B $ AST.pureB x-    bf <*> bx = B $ AST.applyB (unB bf) (unB bx)+    pure x    = B $ Prim.pureB x+    bf <*> bx = B $ Prim.applyB (unB bf) (unB bx)  instance Functor (Behavior t) where     fmap = liftA@@ -241,6 +282,21 @@ {-----------------------------------------------------------------------------     Derived Combinators ------------------------------------------------------------------------------}+{-++Unfortunately, we can't make a  Num  instance because that would+require  Eq  and  Show .++instance Num a => Num (Behavior t a) where+    (+) = liftA2 (+)+    (-) = liftA2 (-)+    (*) = liftA2 (*)+    negate = fmap negate+    abs    = fmap abs+    signum = fmap signum+    fromInteger = pure . fromInteger+-}+ -- | Keep only the 'Just' values. -- Variant of 'filterE'. filterJust :: Event t (Maybe a) -> Event t a@@ -258,6 +314,8 @@ whenE bf = filterApply (const <$> bf)  -- | Split event occurrences according to a tag.+-- The 'Left' values go into the left component while the 'Right' values+-- go into the right component of the result. split :: Event t (Either a b) -> (Event t a, Event t b) split e = (filterJust $ fromLeft <$> e, filterJust $ fromRight <$> e)     where@@ -271,7 +329,7 @@ -- -- > unionWith f e1 e2 = fmap (foldr1 f) <$> collect (e1 `union` e2) unionWith :: (a -> a -> a) -> Event t a -> Event t a -> Event t a-unionWith f e1 e2 = E $ AST.unionWith g (unE e1) (unE e2)+unionWith f e1 e2 = E $ Prim.unionWith g (unE e1) (unE e2)     where g xs ys = singleton $ foldr1 f (xs ++ ys)  -- | Keep only the last occurrence when simultaneous occurrences happen.
src/Reactive/Banana/Frameworks.hs view
@@ -1,14 +1,15 @@ {------------------------------------------------------------------------------    Reactive Banana+    reactive-banana ------------------------------------------------------------------------------}-{-# LANGUAGE Rank2Types, DeriveDataTypeable #-}+{-# LANGUAGE CPP, Rank2Types #-}+-- #define UseExtensions 1  module Reactive.Banana.Frameworks (     -- * Synopsis     -- | Build event networks using existing event-based frameworks and run them.          -- * Simple use-    interpret, interpretAsHandler,+    interpretAsHandler,      -- * Building event networks with input/output     -- $build@@ -23,36 +24,69 @@     -- * Utilities     -- $utilities     newAddHandler, newEvent,+    +    -- * Internal+    interpretFrameworks,     ) where  import Control.Applicative-import Control.Arrow (second)-import Control.Concurrent+import Control.Concurrent.MVar import Control.Monad import Control.Monad.Fix       (MonadFix(..)) import Control.Monad.IO.Class  (MonadIO(..)) import Control.Monad.Trans.RWS -import Data.Dynamic (Typeable) import Data.IORef-import Data.List (nub) import Data.Monoid--import qualified Data.HashMap.Strict as Map+import qualified Data.Unique -- ordinary uniques here, because they are Ord  import Reactive.Banana.Internal.InputOutput import Reactive.Banana.Combinators-import qualified Reactive.Banana.Internal.AST as AST++#if UseExtensions++import qualified Reactive.Banana.Internal.AST as Prim import qualified Reactive.Banana.Internal.PushGraph as Implementation -type Map = Map.HashMap+#else +import qualified Reactive.Banana.Model as Prim+import Reactive.Banana.Internal.CompileModel++#endif++import qualified Data.Map as Map++type Map = Map.Map+ {------------------------------------------------------------------------------    AST specific functions+    Compilation specific to the different backends ------------------------------------------------------------------------------}-inputE :: InputChannel [a] -> Event t a-inputE = E . AST.inputE+#if UseExtensions +-- TODO: Share types. For that, Model.Event would need to become a newtype.++data InputToEvent = InputToEvent (forall a. InputChannel a -> PrimEvent a)+type Compile a b  = (InputToEvent -> IO (PrimEvent a,b)) -> IO (Automaton a,b)++compileWithGlobalInput :: Compile a b+compileWithGlobalInput f = do+    (e,b) <- f (InputToEvent Prim.inputE)+    a     <- Implementation.compileToAutomaton e +    return (a, b)++primChanges ~(Prim.Pair _ (Prim.Stepper x e)) = e+primInitial ~(Prim.Pair _ (Prim.Stepper x e)) = x++#else++-- types are imported by CompileModel++primChanges ~(Prim.StepperB x e) = e+primInitial ~(Prim.StepperB x e) = x++#endif+ {-----------------------------------------------------------------------------     NetworkDescription, setting up event networks ------------------------------------------------------------------------------}@@ -63,7 +97,7 @@     like @wxHaskell@ or @Gtk2Hs@.     How do you do that? -    This "Reactive.Banana.Implementation" module allows you to obtain /input/ events+    The module presented here allows you to obtain /input/ events     from external sources     and it allows you perform /output/ in reaction to events.     @@ -137,8 +171,9 @@ -- values of types 'Event' or 'Behavior' -- outside the 'NetworkDescription' monad. newtype NetworkDescription t a-    = Prepare { unPrepare :: RWST () (Preparations t) () IO a }+    = Prepare { unPrepare :: RWST InputToEvent (Preparations t) () IO a } +-- boilerplate class instances instance Monad (NetworkDescription t) where     return  = Prepare . return     m >>= k = Prepare $ unPrepare m >>= unPrepare . k@@ -192,12 +227,20 @@ -- this will register a callback function such that -- an event will occur whenever the callback function is called. fromAddHandler :: AddHandler a -> NetworkDescription t (Event t a)-fromAddHandler addHandler = Prepare $ do-    i <- liftIO $ newInputChannel+fromAddHandler addHandler = do+    (i,e) <- newInput     let addHandler' k = addHandler $ k . toValue i . (\x -> [x])-    tell ([],[addHandler'],[],[])-    return $ inputE i+    Prepare $ tell ([],[addHandler'],[],[])+    return e +-- create a new input event from the global input event+newInput :: NetworkDescription t (InputChannel [a], Event t a)+newInput = Prepare $ do+    i <- liftIO newInputChannel+    (InputToEvent f) <- ask+    return (i, E $ f i)++ -- | Input, -- obtain a 'Behavior' by frequently polling mutable data, like the current time. --@@ -211,12 +254,12 @@ -- it should not perform expensive computations. -- Neither should its side effects affect the event network significantly. fromPoll :: IO a -> NetworkDescription t (Behavior t a)-fromPoll poll = Prepare $ do-    i <- liftIO $ newInputChannel+fromPoll poll = do+    (i,e) <- newInput     let poll' = toValue i . (:[]) <$> poll-    tell ([],[],[poll'],[])+    Prepare $ tell ([],[],[poll'],[])     initial <- liftIO $ poll-    return $ stepper initial (inputE i)+    return $ stepper initial e  -- | Input, -- obtain a 'Behavior' from an 'AddHandler' that notifies changes.@@ -238,7 +281,7 @@ -- -- > changes (stepper x e) = return (calm e) changes :: Behavior t a -> NetworkDescription t (Event t a)-changes ~(B (AST.Pair _ (AST.Stepper x e))) = return $ E $ fmap (:[]) e+changes = return . E . Prim.mapE (:[]) . primChanges . unB  -- | Output, -- observe the initial value contained in a 'Behavior'.@@ -246,7 +289,7 @@ -- Similar to 'updates', this function is not well-defined, -- but exists for reasons of efficiency. initial :: Behavior t a -> NetworkDescription t a-initial ~(B (AST.Pair _ (AST.Stepper x e))) = return x+initial = return . primInitial . unB  -- | Lift an 'IO' action into the 'NetworkDescription' monad, -- but defer its execution until compilation time.@@ -257,24 +300,24 @@ -- | Compile a 'NetworkDescription' into an 'EventNetwork' -- that you can 'actuate', 'pause' and so on. compile :: (forall t. NetworkDescription t ()) -> IO EventNetwork-compile (Prepare m) = do-    -- execute the NetworkDescription monad-    (_,_,(outputs,inputs,polls,liftIOs)) <- runRWST m () ()-    sequence_ liftIOs-    -    let -- union of all  reactimates-        E graph = foldr union never outputs-    -    automaton <- Implementation.compileToAutomaton graph-    +compile m = do++    -- compile network description into an automaton+    (automaton,(inputs,polls)) <- compileWithGlobalInput $ \inputToEvent -> do+        -- execute the NetworkDescription monad+        (_,_,(outputs,inputs,polls,liftIOs)) <- runRWST (unPrepare m) inputToEvent ()+        sequence_ liftIOs                   -- execute the late IOs+        let E e = foldr union never outputs -- union of all the reactimates+        return (e, (inputs, polls))+             -- allocate new variable for the automaton     rautomaton <- newEmptyMVar     putMVar rautomaton automaton     -    let -- run the graph on a single input value+    let -- run the automaton on a single input value         run :: InputValue -> IO ()         run input = do-            -- takeMVar  makes sure that event graph updates are sequential.+            -- takeMVar  makes sure that event graph updates are atomic             automaton  <- takeMVar rautomaton             -- poll mutable data             pollValues <- sequence polls@@ -318,7 +361,7 @@     -- The network will /not/ stop immediately though, only after     -- the current event has been processed completely.     pause :: IO ()-    } deriving (Typeable)+    }  -- Make an event network from a function that registers all event handlers makeEventNetwork :: IO (IO ()) -> IO EventNetwork@@ -334,10 +377,10 @@ {-----------------------------------------------------------------------------     Simple use ------------------------------------------------------------------------------}--- | Simple way to run an event graph. Very useful for testing.--- Uses the efficient push-driven implementation.-interpret :: (forall t. Event t a -> Event t b) -> [a] -> IO [[b]]-interpret f xs = do+-- | Interpret by using a framework internally.+-- Only useful for testing library internals.+interpretFrameworks :: (forall t. Event t a -> Event t b) -> [a] -> IO [[b]]+interpretFrameworks f xs = do     output                    <- newIORef []     (addHandler, runHandlers) <- newAddHandler     network                   <- compile $ do@@ -383,7 +426,7 @@ newAddHandler = do     handlers <- newIORef Map.empty     let addHandler k = do-            key <- newUnique+            key <- Data.Unique.newUnique             modifyIORef handlers $ Map.insert key k             return $ modifyIORef handlers $ Map.delete key         runHandlers x =
src/Reactive/Banana/Internal/AST.hs view
@@ -1,5 +1,5 @@ {------------------------------------------------------------------------------    Reactive-Banana+    reactive-banana ------------------------------------------------------------------------------} {-# LANGUAGE GADTs, TypeFamilies, TupleSections, EmptyDataDecls,     TypeSynonymInstances, FlexibleInstances #-}@@ -11,8 +11,10 @@ import qualified Data.Vault as Vault import System.IO.Unsafe +import Data.Unique.Really import Data.Hashable +import qualified Reactive.Banana.Model as Model import Reactive.Banana.Internal.InputOutput  {-----------------------------------------------------------------------------@@ -31,7 +33,7 @@     AccumE    :: a -> Event t (a -> a) -> EventD t a          InputE    :: InputChannel a   -> EventD t a   -- represent external inputs-    InputPure :: InputChannel (EventModel a)+    InputPure :: InputChannel (Model.Event a)               -> EventD t a                       -- input for model implementation  -- | Constructors for behaviors.@@ -116,7 +118,10 @@ stepperB acc e    = shareB $ Stepper acc (unE e) inputB i          = shareB $ InputB i +-- functor mapE f  = applyE (pureB f)++-- applicative functor pureB x = stepperB x never  applyB :: Behavior Expr (a -> b) -> Behavior Expr a -> Behavior Expr b@@ -128,12 +133,7 @@     changeR x (f,_) = (f,x) applyB _ _ = error "TODO: Don't know what to do with external behaviors." --- instance Functor (Event Expr) where-instance Functor (Pair Node (EventD Expr)) where-    fmap   = mapE--- instance Functor (Behavior Expr) where-instance Functor (Pair Node (BehaviorD Expr)) where-    fmap f = applyB (pureB f)+mapB f = applyB (pureB f)  {-----------------------------------------------------------------------------     The 'Node' type is used for observable sharing and must be defined here.@@ -150,21 +150,14 @@     , keyFormula :: !(Vault.Key (FormulaD Nodes a))     , keyOrder   :: !Unique       -- use for Reactive.Banana.Internal.Model-    , keyModelE  :: !(Vault.Key (EventModel a))-    , keyModelB  :: !(Vault.Key (BehaviorModel a))+    , keyModelE  :: !(Vault.Key (Model.Event a))+    , keyModelB  :: !(Vault.Key (Model.Behavior a))     }  newNode :: IO (Node a) newNode = Node     <$> Vault.newKey <*> Vault.newKey <*> newUnique     <*> Vault.newKey <*> Vault.newKey--{------------------------------------------------------------------------------    Reactive.Banana.Internal.Model-------------------------------------------------------------------------------}--- we have to define the interpretation types here-type EventModel a    = [Maybe a]-data BehaviorModel a = StepperB a (EventModel a)  {-----------------------------------------------------------------------------     Reactive.Banana.Internal.PushGraph
+ src/Reactive/Banana/Internal/CompileModel.hs view
@@ -0,0 +1,62 @@+{-----------------------------------------------------------------------------+    reactive-banana+------------------------------------------------------------------------------}+{-# LANGUAGE Rank2Types, ScopedTypeVariables #-}++module Reactive.Banana.Internal.CompileModel (+    -- * Synopsis+    -- Compile model implementation to automaton.+    +    InputToEvent(..), Compile, compileWithGlobalInput,+    ) where++import Control.Applicative+import Control.Exception (evaluate)+import Control.Monad+import Control.Monad.Trans.Reader+import Data.IORef+import Data.Maybe+import System.IO.Unsafe++import Reactive.Banana.Internal.InputOutput+import Reactive.Banana.Model++{-----------------------------------------------------------------------------+    Compile model to an automaton+------------------------------------------------------------------------------}+data InputToEvent = InputToEvent (forall a. InputChannel a -> Event a)+type Compile a b  = (InputToEvent -> IO (Event a,b)) -> IO (Automaton a,b)++compileWithGlobalInput :: Compile a b+compileWithGlobalInput f = do+    -- reference that holds input values+    (ref    :: IORef [InputValue]) <- newIORef undefined+    -- An infinite list of all future input values. Very unsafe!+    (inputs :: Event [InputValue]) <- unsafeSequence (Just <$> readIORef ref)+    +    let+        inputToEvent = InputToEvent $+            \i -> filterJust $ mapE (fromInputValues i) inputs+    +        filterJust = map fromJust . filter isJust+        +        fromInputValues :: InputChannel a -> [InputValue] -> Maybe a+        fromInputValues i xs = listToMaybe [y | x <- xs, Just y <- [fromValue i x]]+            ++        -- step of the automaton+        step values outputs = do+            writeIORef ref values           -- write new input value+            (o:outputs) <- evaluate outputs -- make sure that output is in WHNF+            return (o, outputs)             -- return result+    +    (outputs, b) <- f inputToEvent+    return $ (fromStateful step outputs, b)+++unsafeSequence :: IO a -> IO [a]+unsafeSequence m = unsafeInterleaveIO $ do+    x  <- m+    xs <- unsafeSequence m+    return (x:xs)+
src/Reactive/Banana/Internal/InputOutput.hs view
@@ -16,38 +16,18 @@     -- | Stepwise execution of an event graph.     Automaton(..), fromStateful, unfoldAutomaton, -    -- * Uniques-    -- | Doesn't belong here, but we also have some stuff about uniques-    -- that we use for observable sharing.-    Unique, newUnique,     ) where  import Control.Applicative import Control.Exception (evaluate) -import qualified Data.Unique as Unique+import Data.Unique.Really import qualified Data.Vault  as Vault-import Data.Hashable-import System.Mem.StableName  {------------------------------------------------------------------------------    Better Uniques-------------------------------------------------------------------------------}-type ReallyUnique = StableName Unique.Unique-type Unique = ReallyUnique---- instance Hashable Unique--newUnique :: IO Unique-newUnique = do-    x <- Unique.newUnique-    evaluate x-    makeStableName x--{-----------------------------------------------------------------------------     Storing heterogenous input values ------------------------------------------------------------------------------}-type Channel  = ReallyUnique    -- identifies an input+type Channel  = Unique          -- identifies an input type Key      = Vault.Key       -- key to retrieve a value type Value    = Vault.Vault     -- value storage 
+ src/Reactive/Banana/Internal/InterpretModel.hs view
@@ -0,0 +1,75 @@+{-----------------------------------------------------------------------------+    reactive-banana+------------------------------------------------------------------------------}+module Reactive.Banana.Internal.InterpretModel (+    -- * Synopsis+    -- | Interpret abstract syntax with model implementation.+    +    interpretModel+    ) where++import Control.Applicative+import Control.Monad+import Control.Monad.Fix+import Control.Monad.Trans.State+import qualified Data.Vault as Vault++import qualified Reactive.Banana.Internal.AST as AST+import Reactive.Banana.Internal.InputOutput+import Reactive.Banana.Model as Model hiding (interpretModel)++{-----------------------------------------------------------------------------+    Interpret AST with model,+    pay attention to observable sharing+------------------------------------------------------------------------------}+-- state monad for evaluation+type Eval = State Vault.Vault++-- | Interpret an event graph with the model implementation.+-- Mainly useful for testing library internals.+interpretModel+    :: (AST.Event AST.Expr a -> AST.Event AST.Expr b)+    -> Model.Event a -> IO (Model.Event b)+interpretModel f input = do+    i0 <- newInputChannel+    +    let+        evalE :: AST.EventD AST.Expr a -> Eval (Model.Event a)+        evalE (AST.Never)             = return $ never+        evalE (AST.UnionWith f e1 e2) = unionWith f <$> goE e1 <*> goE e2+        evalE (AST.FilterE p e)       = filterE p   <$> goE e+        evalE (AST.ApplyE b e )       = applyE      <$> goB b  <*> goE e+        evalE (AST.AccumE x e )       = accumE x    <$> goE e+        evalE (AST.InputPure i)       =+            return $ maybe err id $ fromValue i (toValue i0 input)+            where err = error "Reactive.Banana.PushIO.interpretModel: internal error: Input"+        evalE _                       =+            error "Reactive.Banana.PushIO.interpretModel: internal error: E"++        evalB :: AST.BehaviorD AST.Expr a -> Eval (Model.Behavior a)+        evalB (AST.Stepper x e) = stepperB x <$> goE e+        evalB _                 =+            error "Reactive.Banana.PushIO.interpretModel: internal error: B"++        goE :: AST.Event AST.Expr a -> Eval (Model.Event a)+        goE (AST.Pair node e) = do+            values <- get+            case Vault.lookup (AST.keyModelE node) values of+                Nothing -> mfix $ \v -> do+                    modify $ Vault.insert (AST.keyModelE node) v+                    evalE e+                Just v  -> return v++        goB :: AST.Behavior AST.Expr a -> Eval (Model.Behavior a)+        goB (AST.Pair node b) = do+            values <- get+            case Vault.lookup (AST.keyModelB node) values of+                Nothing -> mfix $ \v -> do+                    modify $ Vault.insert (AST.keyModelB node) v+                    evalB b+                Just v  -> return v+    +    return $+        zipWith const+            (evalState (goE $ f $ AST.inputPure i0) Vault.empty)+            input
− src/Reactive/Banana/Internal/Model.hs
@@ -1,141 +0,0 @@-{------------------------------------------------------------------------------    Reactive-Banana-------------------------------------------------------------------------------}-{-# LANGUAGE GADTs #-}-module Reactive.Banana.Internal.Model (-    -- * Synopsis-    -- | Model implementation of the abstract syntax tree.-    -    -- * Description-    -- $model--    -- Combinators-    -- Event(..), Behavior(..),-    -- never, filterE, unionWith, applyE, accumE, stepper,-   -    -- * Interpretation-    interpretModel,-    ) where--import Control.Applicative-import Control.Monad-import Control.Monad.Fix-import Control.Monad.Trans.State-import qualified Data.Vault as Vault--import qualified Reactive.Banana.Internal.AST as AST-import Reactive.Banana.Internal.InputOutput--{-$model--This module contains the model implementation for the primitive combinators-defined "Reactive.Banana.Internal.AST"-which in turn are the basis for the official combinators-documented in "Reactive.Banana.Combinators".--This module does not export any combinators,-you have to look at the source code to make use of it.-(If there is no link to the source code at every type signature,-then you have to run cabal with --hyperlink-source flag.)--This model is /authoritative/: when observed with the 'interpretModel' function,-both the actual implementation and its model /must/ agree on the result.-Note that this must also hold for recursive and partial definitions-(at least in spirit, I'm not going to split hairs over @_|_@ vs @\\_ -> _|_@).--Concerning time and space complexity, the model is not authoritative, however.-Implementations are free to be much more efficient.--}--{------------------------------------------------------------------------------    Combinators-------------------------------------------------------------------------------}--- due to observable sharing, the types have to be imported from--- the module Reactive.Banana.Internal.AST-type Event a    = AST.EventModel a     -- = [Maybe a]-type Behavior a = AST.BehaviorModel a  -- = StepperB a (Event a)--never :: Event a-never = repeat Nothing--filterE :: (a -> Bool) -> Event a -> Event a-filterE p = map (>>= \x -> if p x then Just x else Nothing)--unionWith :: (a -> a -> a) -> Event a -> Event a -> Event a-unionWith f = zipWith g-    where-    g (Just x) (Just y) = Just $ f x y-    g (Just x) Nothing  = Just x-    g Nothing  (Just y) = Just y-    g Nothing  Nothing  = Nothing--applyE :: Behavior (a -> b) -> Event a -> Event b-applyE _                   []     = []-applyE (AST.StepperB f fe) (x:xs) = fmap f x : applyE (step f fe) xs-    where-    step a (Nothing:b) = stepper a b-    step _ (Just a :b) = stepper a b--accumE :: a -> Event (a -> a) -> Event a-accumE x []           = []-accumE x (Nothing:fs) = Nothing : accumE x fs-accumE x (Just f :fs) = let y = f x in y `seq` (Just y:accumE y fs) --stepper :: a -> [Maybe a] -> Behavior a-stepper = AST.StepperB--{------------------------------------------------------------------------------    Interpretation, pays attention to observable sharing-------------------------------------------------------------------------------}--- state monad for evaluation-type Eval = State Vault.Vault---- | Interpret an event graph with the model implementation.--- Mainly useful for testing library internals.-interpretModel-    :: (AST.Event AST.Expr a -> AST.Event AST.Expr b)-    -> Event a -> IO (Event b)-interpretModel f input = do-    i0 <- newInputChannel-    -    let-        evalE :: AST.EventD AST.Expr a -> Eval (Event a)-        evalE (AST.Never)             = return $ never-        evalE (AST.UnionWith f e1 e2) = unionWith f <$> goE e1 <*> goE e2-        evalE (AST.FilterE p e)       = filterE p   <$> goE e-        evalE (AST.ApplyE b e )       = applyE      <$> goB b  <*> goE e-        evalE (AST.AccumE x e )       = accumE x    <$> goE e-        evalE (AST.InputPure i)       =-            return $ maybe err id $ fromValue i (toValue i0 input)-            where err = error "Reactive.Banana.PushIO.interpretModel: internal error: Input"-        evalE _                       =-            error "Reactive.Banana.PushIO.interpretModel: internal error: E"--        evalB :: AST.BehaviorD AST.Expr a -> Eval (Behavior a)-        evalB (AST.Stepper x e) = stepper x <$> goE e-        evalB _                 =-            error "Reactive.Banana.PushIO.interpretModel: internal error: B"--        goE :: AST.Event AST.Expr a -> Eval (Event a)-        goE (AST.Pair node e) = do-            values <- get-            case Vault.lookup (AST.keyModelE node) values of-                Nothing -> mfix $ \v -> do-                    modify $ Vault.insert (AST.keyModelE node) v-                    evalE e-                Just v  -> return v--        goB :: AST.Behavior AST.Expr a -> Eval (Behavior a)-        goB (AST.Pair node b) = do-            values <- get-            case Vault.lookup (AST.keyModelB node) values of-                Nothing -> mfix $ \v -> do-                    modify $ Vault.insert (AST.keyModelB node) v-                    evalB b-                Just v  -> return v-    -    return $-        zipWith const-            (evalState (goE $ f $ AST.inputPure i0) Vault.empty)-            input-
+ src/Reactive/Banana/Model.hs view
@@ -0,0 +1,94 @@+{-----------------------------------------------------------------------------+    reactive-banana+------------------------------------------------------------------------------}+module Reactive.Banana.Model (+    -- * Synopsis+    -- | Model implementation of the abstract syntax tree.+    +    -- * Description+    -- $model++    -- * Combinators+    Event(..), Behavior(..),+    never, filterE, unionWith, applyE, accumE, stepperB,+    mapE, pureB, applyB, mapB,+    +    -- * Interpretation+    interpretModel,+    ) where++import Control.Applicative++{-$model++This module contains the model implementation for the primitive combinators+defined "Reactive.Banana.Internal.AST"+which in turn are the basis for the official combinators+documented in "Reactive.Banana.Combinators".++Look at the source code to make maximal use of this module.+(If there is no link to the source code at every type signature,+then you have to run cabal with --hyperlink-source flag.)++This model is /authoritative/: when observed with the 'interpretModel' function,+both the actual implementation and its model /must/ agree on the result.+Note that this must also hold for recursive and partial definitions+(at least in spirit, I'm not going to split hairs over @_|_@ vs @\\_ -> _|_@).++Concerning time and space complexity, the model is not authoritative, however.+Implementations are free to be much more efficient.+-}++{-----------------------------------------------------------------------------+    Combinators+------------------------------------------------------------------------------}+type Event a    = [Maybe a]+data Behavior a = StepperB a (Event a)++never :: Event a+never = repeat Nothing++filterE :: (a -> Bool) -> Event a -> Event a+filterE p = map (>>= \x -> if p x then Just x else Nothing)++unionWith :: (a -> a -> a) -> Event a -> Event a -> Event a+unionWith f = zipWith g+    where+    g (Just x) (Just y) = Just $ f x y+    g (Just x) Nothing  = Just x+    g Nothing  (Just y) = Just y+    g Nothing  Nothing  = Nothing++applyE :: Behavior (a -> b) -> Event a -> Event b+applyE _               []     = []+applyE (StepperB f fe) (x:xs) = fmap f x : applyE (step f fe) xs+    where+    step a (Nothing:b) = stepperB a b+    step _ (Just a :b) = stepperB a b++accumE :: a -> Event (a -> a) -> Event a+accumE x []           = []+accumE x (Nothing:fs) = Nothing : accumE x fs+accumE x (Just f :fs) = let y = f x in y `seq` (Just y:accumE y fs) ++stepperB :: a -> [Maybe a] -> Behavior a+stepperB = StepperB++-- functor+mapE f  = applyE (pureB f)++-- applicative functor+pureB x = stepperB x never++applyB :: Behavior (a -> b) -> Behavior a -> Behavior b+applyB (StepperB f fe) (StepperB x xe) =+    stepperB (f x) $ mapE (uncurry ($)) pair+    where+    pair = accumE (f,x) $ unionWith (.) (mapE changeL fe) (mapE changeR xe)+    changeL f (_,x) = (f,x)+    changeR x (f,_) = (f,x)++mapB f = applyB (pureB f)++interpretModel :: (Event a -> Event b) -> Event a -> IO (Event b)+interpretModel = (return .)
src/Reactive/Banana/Tests.hs view
@@ -10,6 +10,7 @@ import Control.Monad (when)  import Reactive.Banana.Combinators+import Reactive.Banana.Frameworks (interpretFrameworks)  -- import Test.QuickCheck -- import Test.QuickCheck.Property@@ -18,12 +19,15 @@     Testing ------------------------------------------------------------------------------} matchesModel :: (Show b, Eq b)-             => (forall t. Event t a -> Event t b) -> [[a]] -> IO Bool+             => (forall t. Event t a -> Event t b) -> [a] -> IO Bool matchesModel f xs = do-    bs1 <- interpretModel     f xs-    bs2 <- interpretPushGraph f xs-    when (bs1 /= bs2) $ print bs1 >> print bs2-    return $ bs1 == bs2+    bs1 <- interpretModel      f (singletons xs)+    bs2 <- interpretPushGraph  f (singletons xs)+    bs3 <- interpretFrameworks f xs+    let bs = [bs1,bs2,bs3]+    let b = all (==bs1) bs+    when (not b) $ mapM_ print bs+    return b  testSuite = do         -- trivial unit tests@@ -43,7 +47,7 @@         --  * quickcheck  test :: (Show b, Eq b) => (forall t. Event t Int -> Event t b) -> IO ()-test f = print =<< matchesModel f (singletons [1..8::Int])+test f = print =<< matchesModel f [1..8::Int]  singletons = map (\x -> [x])