Personal tools

DataDriven

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


Contents

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

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

3 Sources

4 Ephemeral