From HaskellWiki
Revision as of 00:21, 10 September 2007 by Conal (talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search


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".


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 whenever e occurs, but whose occurrence values come from applying f to the values from e. (Fran's (==>).)
  • Monoid: mempty is the event that never occurs, and e `mappend` e' is the event that combines occurrences from e and e'. (Fran's neverE 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). In e >>= f, each occurrence of e leads, through f to a new event. Similarly for join, which is somehow simpler for me to think about. The occurrences of e >>= 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
   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, [ 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 to mempty has no effect, since the mempty is guaranteed never to occur.
  • Subscribing l to ea `mappend` eb subscribes l to each of ea and eb.
  • Subscribing l to fmap f e subscribes l . f to e.
  • Subscribing l to join e subscribes to e a listener that subscribes to every event generated by e. (Similarly for e >>= 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.