DataDriven
Abstract
DataDriven is a library for functional events and time-varying values. The ideas and interface fit with functional reactive programming (FRP). Most FRP implementations I'm aware of have a demand-driven implementation, while the implementation of DataDriven is data-driven (surprise). DataDriven is a resurrection of some ideas from an old, incomplete Fran reimplementation that also became the basis of Meurig Sage's [FranTk]. I've been particularly interested in using standard classes as much as possible, most centrally, Monoid and applicative functors and monoids. These ideas were also the motivating application for "Stretching the storage manager: weak pointers and stable names in Haskell".
- Read the Haddock docs (with source code, additional examples, and Comment/Talk links).
- Get the code repository: darcs get http://darcs.haskell.org/packages/DataDriven, or
- Grab a distribution tarball.
- See the version history.
Events
The Data.Event
module defines a notion of functional, composable events, with a data-driven implementation. Most of the ideas and vocabulary are borrowed from Fran, when Fran's events came to mean multiple occurrences (see Declarative Event-Oriented Programming, rather than the initial ICFP '97 publication). As in Fran, you can think of an event as a stream of "occurrences", each of which has a time and a value. The implementation, however, is radically different from Fran's, as it is data-driven rather than demand-driven. And in some cases, the functions are not pure. There are also several event-related functions described in the Sources below, to create time-varying values.
Some of the useful event operations come through standard classes.
Functor
:fmap f e
is the event that occurs whenevere
occurs, but whose occurrence values come from applyingf
to the values frome
. (Fran's(==>)
.)Monoid
:mempty
is the event that never occurs, ande `mappend` e'
is the event that combines occurrences frome
ande'
. (Fran'sneverE
and(.|.)
.)Monad
:return a
is an event with a single occurrence. This one doesn't quite fit the original semantics, as the occurrence is delivered immediately on "listening" to an event (discussed later). Ine >>= f
, each occurrence ofe
leads, throughf
to a new event. Similarly forjoin
, which is somehow simpler for me to think about. The occurrences ofe >>= f
correspond to the union of the occurrences of all such events. For example, suppose we're playing Asteroids and tracking collisions. Each collision can break an asteroid into more of them, each of which has to be tracked for more collisions. Another example: A chat room has an "enter" event, whose occurrences contain new events like "speak".
As a simple example, the following function transforms and combines two events:
show2 :: (Show a, Show b) => Event a -> Event b -> Event String
show2 ea eb = showE ea `mappend` showE eb
where
showE e = fmap show e
The Event
type is not actually a new type, but merely a generalization of the familiar type of continuation-based computations, [http://www.haskell.org/ghc/docs/latest/html/libraries/mtl/Control-Monad-Cont.html Cont]
:
newtype Cont o a = Cont { runCont :: (a -> o) -> o }
The Functor
and Monad
instances come from Cont
. The Monoid
instance for Cont
is missing (as of 2007-09-08), so it is defined in this module (and thus is an "orphan"). The more specialized event type is simply
type Event = Cont Action
where, to save typing later,
type Action = IO ()
Why does it make sense to think of continuation-based computations as events? Because an event is something that one can subscribe to. Subscription provides a "listener" (a continuation) to be invoked on every occurrence of the event. The Monoid
, Functor
, and Monad
operations are simple. Given a listener l :: a -> o
,
- Subscribing
l
tomempty
has no effect, since themempty
is guaranteed never to occur. - Subscribing
l
toea `mappend` eb
subscribesl
to each ofea
andeb
. - Subscribing
l
tofmap f e
subscribesl . f
toe
. - Subscribing
l
tojoin e
subscribes toe
a listener that subscribes to every event generated bye
. (Similarly fore >>= f == join (fmap f e)
.)
The functions in the Event
module operate on this general notion of events (Cont
) or something more specialized. I expect the most common use to be the Event
(IO
) specialization, and the types are often much easier to read for that type. General functions are given general signatures, with the Event
specializations as comments.