- module Control.Applicative
- class (Functor (Event f), Functor (Behavior f), Applicative (Behavior f)) => FRP f where
- never :: Event f a
- union :: Event f a -> Event f a -> Event f a
- apply :: Behavior f (a -> b) -> Event f a -> Event f b
- filterE :: (a -> Bool) -> Event f a -> Event f a
- filterApply :: Behavior f (a -> Bool) -> Event f a -> Event f a
- stepper :: a -> Event f a -> Behavior f a
- accumB :: a -> Event f (a -> a) -> Behavior f a
- accumE :: a -> Event f (a -> a) -> Event f a
- data family Event f :: * -> *
- data family Behavior f :: * -> *
- whenE :: FRP f => Behavior f Bool -> Event f a -> Event f a
- mapAccum :: FRP f => acc -> Event f (acc -> (x, acc)) -> (Event f x, Behavior f acc)
- data Model
- type Time = Double
- interpretTime :: (Event Model a -> Event Model b) -> [(Time, a)] -> [(Time, b)]
- interpret :: (Event Model a -> Event Model b) -> [a] -> [[b]]
Combinators for building event networks and their semantics.
FRP class defines the primitive API for functional reactive programming.
f defines two type constructors
Event f and
and corresponding combinators.
Event f a represents a stream of events as they occur in time.
Semantically, you can think of
Event f a as an infinite list of values
that are tagged with their corresponding time of occurence,
type Event f a = [(Time,a)]
Behavior f a represents a value that varies in time. Think of it as
type Behavior f a = Time -> a
While these type synonyms are the way you should think about
Event, they are a bit vague for formal manipulation.
To remedy this, the library provides a very simple model implementation,
This model is authoritative: every instance of the
FRP class must
give the same results as the model when observed with the
Note that this must also hold for recursive and partial definitions
(at least in spirit, I'm not going to split hairs over
\_ -> _|_).
Concerning time and space complexity, the model is not authoritative, however. Implementations are free to be much more efficient.
Event that never occurs.
Think of it as
never = .
Merge two event streams of the same type. In case of simultaneous occurrences, the left argument comes first. Think of it as
union ((timex,x):xs) ((timey,y):ys) | timex <= timey = (timex,x) : union xs ((timey,y):ys) | timex > timey = (timey,y) : union ((timex,x):xs) ys
Apply a time-varying function to a stream of events. Think of it as
apply bf ex = [(time, bf time x) | (time, x) <- ex]
Allow all events that fulfill the predicate, discard the rest. Think of it as
filterE p es = [(time,a) | (time,a) <- es, p a]
Allow all events that fulfill the time-varying predicate, discard the rest.
It's a slight generalization of
Construct a time-varying function from an initial value and a stream of new values. Think of it as
stepper x0 ex = \time -> last (x0 : [x | (timex,x) <- ex, timex < time])
Note that the smaller-than-sign in the comparision
timex < time means
that the value of the behavior changes "slightly after"
the event occurrences. This allows for recursive definitions.
Also note that in the case of simultaneous occurrences, only the last one is kept.
accumB function is similar to a strict left fold,
It starts with an initial value and combines it with incoming events.
For example, think
accumB "x" [(time1,(++"y")),(time2,(++"z"))] = stepper "x" [(time1,"xy"),(time2,"xyz")]
Note that the value of the behavior changes "slightly after" the events occur. This allows for recursive definitions.
Further combinators that Haddock can't document properly.
instance FRP f => Monoid (Event f a)
instance FPR f => Applicative (Behavior f)
Behavior is an applicative functor. In particular, we have the following functions.
pure :: FRP f => a -> Behavior f a
The constant time-varying value. Think of it as
pure x = \time -> x.
(<*>) :: FRP f => Behavior f (a -> b) -> Behavior f a -> Behavior f b
Combine behaviors in applicative style.
Think of it as
bf <*> bx = \time -> bf time $ bx time.
The type index
Model represents the model implementation.
You are encouraged to look at the source code!
(If there is no link to the source code at every type signature,
then you have to run
Interpreter that corresponds to your mental model.