reactive-banana 1.0.0.0 → 1.0.0.1
raw patch · 5 files changed
+136/−75 lines, 5 filesdep ~transformersPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependency ranges changed: transformers
API changes (from Hackage documentation)
- Reactive.Banana.Combinators: instance GHC.Base.Applicative Reactive.Banana.Types.Behavior
- Reactive.Banana.Combinators: instance GHC.Base.Functor Reactive.Banana.Types.Behavior
- Reactive.Banana.Combinators: instance GHC.Base.Functor Reactive.Banana.Types.Event
Files
- CHANGELOG.md +8/−0
- reactive-banana.cabal +2/−11
- src/Reactive/Banana/Combinators.hs +62/−43
- src/Reactive/Banana/Frameworks.hs +26/−12
- src/Reactive/Banana/Types.hs +38/−9
CHANGELOG.md view
@@ -1,6 +1,14 @@ Changelog for the `reactive-banana` package ------------------------------------------- +**version 1.0.0.1**++* Improve documentation.+ * Add prose section on recursion.+ * Improve explanation for the `changes` function.+* Bump `transfomers` dependency.+* Remove defunct `UseExtensions` flag from cabal file.+ **version 1.0.0.0** The API has been redesigned significantly in this version!
reactive-banana.cabal view
@@ -1,5 +1,5 @@ Name: reactive-banana-Version: 1.0.0.0+Version: 1.0.0.1 Synopsis: Library for functional reactive programming (FRP). Description: Reactive-banana is a library for Functional Reactive Programming (FRP).@@ -37,27 +37,18 @@ location: git://github.com/HeinrichApfelmus/reactive-banana.git subdir: reactive-banana/ -flag UseExtensions- description: Use GHC-specific language extensions.- 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,--- otherwise it is set to False .- Library default-language: Haskell98 hs-source-dirs: src build-depends: base >= 4.2 && < 5, containers >= 0.5 && < 0.6,- transformers >= 0.2 && < 0.5,+ transformers >= 0.2 && < 0.6, vault == 0.3.*, unordered-containers >= 0.2.1.0 && < 0.3, hashable >= 1.1 && < 1.3, pqueue >= 1.0 && < 1.4 --- CPP-options: -DUseExtensions- exposed-modules: Control.Event.Handler, Reactive.Banana,
src/Reactive/Banana/Combinators.hs view
@@ -14,16 +14,21 @@ interpret, -- ** First-order+ -- | This subsections lists the primitive first-order combinators for FRP.+ -- The 'Functor' and 'Applicative' instances are also part of this,+ -- but they are documented at the types 'Event' and 'Behavior'. module Control.Applicative, module Data.Monoid, never, unionWith, filterE, apply,- -- $classes -- ** Moment and accumulation Moment, MonadMoment(..), accumE, stepper, + -- ** Recursion+ -- $recursion+ -- ** Higher-order valueB, valueBLater, observeE, switchE, switchB, @@ -110,35 +115,6 @@ apply :: Behavior (a -> b) -> Event a -> Event b apply bf ex = E $ Prim.applyE (unB bf) (unE ex) -{-$classes--/Further combinators that Haddock can't document properly./--> instance Applicative Behavior--'Behavior' is an applicative functor. In particular, we have the following functions.--> pure :: a -> Behavior a--The constant time-varying value. Semantically, @pure x = \\time -> x@.--> (<*>) :: Behavior (a -> b) -> Behavior a -> Behavior b--Combine behaviors in applicative style.-The semantics are: @bf \<*\> bx = \\time -> bf time $ bx time@.---}--instance Functor Event where- fmap f = E . Prim.mapE f . unE--instance Applicative Behavior where- pure x = B $ Prim.pureB x- bf <*> bx = B $ Prim.applyB (unB bf) (unB bx)--instance Functor Behavior where- fmap = liftA- -- | Construct a time-varying function from an initial value and -- a stream of new values. The result will be a step function. -- Semantically,@@ -156,6 +132,7 @@ -- In the illustration, this is indicated by the dots at the end -- of each step. -- This allows for recursive definitions.+-- See the discussion below for more on recursion. stepper :: MonadMoment m => a -> Event a -> m (Behavior a) stepper a = liftMoment . M . fmap B . Prim.stepperB a . unE @@ -172,26 +149,68 @@ -- > = trimE [(time1,"xy"),(time2,"xyz")] -- > where -- > trimE e start = [(time,x) | (time,x) <- e, start <= time]--------- Note: It makes sense to list the 'accumE' function as a primitive--- combinator, but keep in mind that it can actually be expressed--- in terms of 'stepper' and 'apply' by using recursion:------ > accumE a e1 = mdo--- > let e2 = (\a f -> f a) <$> b <@> e1--- > b <- stepper a e2--- > return e2--- accumE :: MonadMoment m => a -> Event (a -> a) -> m (Event a) accumE acc = liftMoment . M . fmap E . Prim.accumE acc . unE +{-$recursion++/Recursion/ is a very important technique in FRP that is not apparent+from the type signatures.++Here is a prototypical example. It shows how the 'accumE' can be expressed+in terms of the 'stepper' and 'apply' functions by using recursion:++> accumE a e1 = mdo+> let e2 = (\a f -> f a) <$> b <@> e1+> b <- stepper a e2+> return e2++(The @mdo@ notation refers to /value recursion/ in a monad.+The 'MonadFix' instance for the 'Moment' class enables this kind of recursive code.)+(Strictly speaking, this also means that 'accumE' is not a primitive,+because it can be expressed in terms of other combinators.)++This general pattern appears very often in practice:+A Behavior (here @b@) controls what value is put into an Event (here @e2@),+but at the same time, the Event contributes to changes in this Behavior.+Modeling this situation requires recursion.++For another example, consider a vending machine that sells banana juice.+The amount that the customer still has to pay for a juice+is modeled by a Behavior @bAmount@.+Whenever the customer inserts a coin into the machine,+an Event @eCoin@ occurs, and the amount will be reduced.+Whenver the amount goes below zero, an Event @eSold@ will occur,+indicating the release of a bottle of fresh banana juice,+and the amount to be paid will be reset to the original price.+The model requires recursion, and can be expressed in code as follows:++> mdo+> let price = 50 :: Int+> bAmount <- accumB price $ unions+> [ subtract 10 <$ eCoin+> , const price <$ eSold ]+> let eSold = whenE ((<= 0) <$> bAmount) eCoin++On one hand, the Behavior @bAmount@ controls whether the Event @eSold@+occcurs at all; the bottle of banana juice is unavailable to penniless customers.+But at the same time, the Event @eSold@ will cause a reset+of the Behavior @bAmount@, so both depend on each other.++Recursive code like this examples works thanks to the semantics of 'stepper'.+In general, /mutual recursion/ between several 'Event's and 'Behavior's+is always well-defined,+as long as an Event depends on itself only /via/ a Behavior,+and vice versa.++-}+ -- | Obtain the value of the 'Behavior' at a given moment in time. -- Semantically, it corresponds to -- -- > valueB b = \time -> b time ----- NOTE: The value is immediately available for pattern matching.+-- Note: The value is immediately available for pattern matching. -- Unfortunately, this means that @valueB@ is unsuitable for use -- with value recursion in the 'Moment' monad. -- If you need recursion, please use 'valueBLater' instead.@@ -203,7 +222,7 @@ -- -- > valueBLater b = \time -> b time ----- NOTE: To allow for more recursion, the value is returned /lazily/+-- Note: To allow for more recursion, the value is returned /lazily/ -- and not available for pattern matching immediately. -- It can be used safely with most combinators like 'stepper'. -- If that doesn't work for you, please use 'valueB' instead.
src/Reactive/Banana/Frameworks.hs view
@@ -197,21 +197,35 @@ stepper initial e -- | Output,--- observe when a 'Behavior' changes.+-- return an 'Event' that is adapted to the changes of a 'Behavior'. ----- Strictly speaking, a 'Behavior' denotes a value that--- varies /continuously/ in time,--- so there is no well-defined event which indicates when the behavior changes.+-- Remember that semantically, a 'Behavior' is a function @Behavior a = Time -> a@.+-- This means that a Behavior does not have a notion of \"changes\" associated with it.+-- For instance, the following Behaviors are equal: ----- Still, for reasons of efficiency, the library provides a way to observe--- changes when the behavior is a step function, for instance as--- created by 'stepper'. There are no formal guarantees,--- but the idea is that+-- > stepper 0 []+-- > = stepper 0 [(time1, 0), (time2, 0)]+-- > = stepper 0 $ zip [time1,time2..] (repeat 0) ----- > changes =<< stepper x e = return e+-- In principle, to perform IO actions with the value of a Behavior,+-- one has to sample it using an 'Event' and the 'apply' function. ----- Note: The values of the event will not become available--- until event processing is complete.+-- However, in practice, Behaviors are usually step functions.+-- For reasons of efficiency, the library provides a way+-- to obtain an Event that /mostly/ coincides with the steps of a Behavior,+-- so that sampling is only done at a few select points in time.+-- The idea is that+--+-- > changes =<< stepper x e = return e+--+-- Please use 'changes' only in a ways that do /not/ distinguish+-- between the different expressions for the same Behavior above.+--+-- Note that the value of the event is actually the /new/ value,+-- i.e. that value slightly after this point in time. (See the documentation of 'stepper').+-- This is more convenient.+-- However, the value will not become available until after event processing is complete;+-- this is indicated by the type 'Future'. -- It can be used only in the context of 'reactimate''. changes :: Behavior a -> MomentIO (Event (Future a)) changes = return . E . Prim.mapE F . Prim.changesB . unB@@ -238,7 +252,7 @@ -- | Impose a different sampling event on a 'Behavior'. ----- The 'Behavior' will vary continuously as before, but the event returned+-- The 'Behavior' will have the same values as before, but the event returned -- by the 'changes' function will now happen simultaneously with the -- imposed event. --
src/Reactive/Banana/Types.hs view
@@ -15,6 +15,10 @@ import qualified Reactive.Banana.Internal.Combinators as Prim +{-----------------------------------------------------------------------------+ Types+------------------------------------------------------------------------------}+ {-| @Event a@ represents a stream of events as they occur in time. Semantically, you can think of @Event a@ as an infinite list of values that are tagged with their corresponding time of occurrence,@@ -30,6 +34,15 @@ newtype Event a = E { unE :: Prim.Event a } -- Invariant: The empty list `[]` never occurs as event value. +-- | The function 'fmap' applies a function @f@ to every value.+-- Semantically,+--+-- > fmap :: (a -> b) -> Event a -> Event b+-- > fmap f e = [(time, f a) | (time, a) <- e]+instance Functor Event where+ fmap f = E . Prim.mapE f . unE++ {-| @Behavior a@ represents a value that varies in time. Semantically, you can think of it as a function @@ -39,8 +52,30 @@ -} newtype Behavior a = B { unB :: Prim.Behavior a } +-- | The function 'pure' returns a value that is constant in time. Semantically,+--+-- > pure :: a -> Behavior a+-- > pure x = \time -> x+--+-- The combinator '<*>' applies a time-varying function to a time-varying value.+--+-- > (<*>) :: Behavior (a -> b) -> Behavior a -> Behavior b+-- > fx <*> bx = \time -> fx time $ bx time+instance Applicative Behavior where+ pure x = B $ Prim.pureB x+ bf <*> bx = B $ Prim.applyB (unB bf) (unB bx)++-- | The function 'fmap' applies a function @f@ at every point in time.+-- Semantically,+--+-- > fmap :: (a -> b) -> Behavior a -> Behavior b+-- > fmap f b = \time -> f (b time)+instance Functor Behavior where+ fmap = liftA++ -- | The 'Future' monad is just a helper type for the 'changes' function.--- +-- -- A value of type @Future a@ is only available in the context -- of a 'reactimate' but not during event processing. newtype Future a = F { unF :: Prim.Future a }@@ -56,8 +91,9 @@ pure = F . pure f <*> a = F $ unF f <*> unF a + {-| The 'Moment' monad denotes a /pure/ computation that happens-at one particular moment in time. Semantically, it as a reader monad+at one particular moment in time. Semantically, it is a reader monad > type Moment a = Time -> a @@ -82,7 +118,6 @@ {-| An instance of the 'MonadMoment' class denotes a computation that happens at one particular moment in time.- Unlike the 'Moment' monad, it need not be pure anymore. -} class Monad m => MonadMoment m where@@ -109,9 +144,3 @@ pure = MIO . pure f <*> a = MIO $ unMIO f <*> unMIO a instance MonadFix MomentIO where mfix f = MIO $ mfix (unMIO . f)---{--instance Frameworks t => MonadIO Moment where- liftIO = M . Prim.liftIONow--}