packages feed

reactive-0.5: src/Data/SReactive.hs

{-# OPTIONS_GHC -Wall #-}
{-# LANGUAGE TypeSynonymInstances, ScopedTypeVariables #-}
----------------------------------------------------------------------
-- |
-- Module      :  Data.SReactive
-- Copyright   :  (c) Conal Elliott 2008
-- License     :  BSD3
-- 
-- Maintainer  :  conal@conal.net
-- Stability   :  experimental
-- 
-- Simple, semantics-based reactive values
----------------------------------------------------------------------

module Data.SReactive
  (
    -- * Primitives
    Reactive'(..), stepper
  , joinR
    -- * Extras (defined via primitives)
  , Reactive
  , switcher, snapshot, snapshot_, whenE
  , accumR, scanlR, monoidR, maybeR, flipFlop, countR, traceR
  ) where

import Control.Applicative
import Control.Monad
import Data.Monoid

-- TypeCompose
import Control.Compose (Unop)
import Data.Pair (Pair(..),pairEdit)

import Data.EventExtras
import Data.Improving


{----------------------------------------------------------
    Primitives
----------------------------------------------------------}

data Reactive' t a =
  Stepper {
    rInit  :: a                         -- ^ initial value
  , rEvent :: Event' t a                -- ^ waiting for event
  }

-- | Reactive value from an initial value and a new-value event.
stepper :: a -> Event' t a -> Reactive' t a
stepper = Stepper


instance Ord t => Pair (Reactive' t) where
  -- pair :: Reactive' t a -> Reactive' t b -> Reactive' t (a,b)
  (c `Stepper` ce) `pair` (d `Stepper` de) =
    (c,d) `accumR` pairEdit (ce,de)

instance Functor (Reactive' t) where
  fmap f (a `Stepper` e) = f a `stepper` fmap f e

instance Ord t => Applicative (Reactive' t) where
  pure a = a `stepper` mempty
  -- Standard definition.  See 'Pair'.
  rf <*> rx = uncurry ($) <$> (rf `pair` rx)

-- A wonderful thing about the <*> definition for Reactive' t is that it
-- automatically caches the previous value of the function or argument
-- when the argument or function changes.

instance Ord t => Monad (Reactive' t) where
  return  = pure
  r >>= f = joinR (f <$> r)

-- | Reactive' t 'join' (equivalent to 'join' but slightly more efficient, I think)
joinR :: Ord t => Reactive' t (Reactive' t a) -> Reactive' t a
joinR ((a `Stepper` e) `Stepper` er) = 
  a `stepper` (e `mappend` join (rToE <$> er))

-- | Turn a reactive value into an event, given a time for the initial
-- occurrence.
rToE :: Ord t => Reactive' t a -> Event' t a
rToE (a `Stepper` e) = pure a `mappend` e

-- e  :: Event' t a
-- er :: Event' t (Reactive' t a)
-- 
-- rToE <$> er ::: Event' t (Event' t a)
-- join (rToE <$> er) ::: Event' t a

-- This variant of 'snapshot' has 'Nothing's where @b@ changed and @a@
-- didn't.
snap :: forall a b t. Ord t =>
        Event' t a -> Reactive' t b -> Event' t (Maybe a, b)
ea `snap` (b0 `Stepper` eb) =
  (Nothing, b0) `accumE` (fmap fa ea `mappend` fmap fb eb)
 where
   fa :: a -> Unop (Maybe a, b)
   fb :: b -> Unop (Maybe a, b)
   fa a (_,b) = (Just a , b)
   fb b _     = (Nothing, b)


{----------------------------------------------------------
    Extras (defined via primitives)
----------------------------------------------------------}

type Reactive = Reactive' (Improving Double)

-- | Snapshot a reactive value whenever an event occurs.
snapshot :: Ord t => Event' t a -> Reactive' t b -> Event' t (a,b)
e `snapshot` r = joinMaybes $ fmap f (e `snap` r)
 where
   f (Nothing,_) = Nothing
   f (Just a ,b) = Just (a,b)

-- | Switch between reactive values.
switcher :: Ord t => Reactive' t a -> Event' t (Reactive' t a) -> Reactive' t a
r `switcher` e = joinR (r `stepper` e)

-- | Like 'snapshot' but discarding event data (often @a@ is @()@).
snapshot_ :: Ord t => Event' t a -> Reactive' t b -> Event' t b
e `snapshot_` src = snd <$> (e `snapshot` src)

-- | Filter an event according to whether a boolean source is true.
whenE :: Ord t => Event' t a -> Reactive' t Bool -> Event' t a
whenE e = joinMaybes . fmap h . snapshot e
 where
   h (a,True)  = Just a
   h (_,False) = Nothing


-- | Reactive' t value from an initial value and an updater event.  See also
-- 'accumE'.
accumR :: Ord t => a -> Event' t (a -> a) -> Reactive' t a
a `accumR` e = a `stepper` (a `accumE` e)

-- | Like 'scanl' for reactive values.  See also 'scanlE'.
scanlR :: Ord t => (a -> b -> a) -> a -> Event' t b -> Reactive' t a
scanlR f a e = a `stepper` scanlE f a e

-- | Accumulate values from a monoid-valued event.  Specialization of
-- 'scanlE', using 'mappend' and 'mempty'.  See also 'monoidE'.
monoidR :: (Ord t, Monoid a) => Event' t a -> Reactive' t a
monoidR = scanlR mappend mempty

-- | Start out blank ('Nothing'), latching onto each new @a@, and blanking
-- on each @b@.  If you just want to latch and not blank, then use
-- 'mempty' for @lose@.
maybeR :: Ord t => Event' t a -> Event' t b -> Reactive' t (Maybe a)
maybeR get lose =
  Nothing `stepper` (fmap Just get `mappend` (Nothing <$ lose))

-- | Flip-flopping source.  Turns true when @ea@ occurs and false when
-- @eb@ occurs.
flipFlop :: Ord t => Event' t a -> Event' t b -> Reactive' t Bool
flipFlop ea eb =
  False `stepper` ((True <$ ea) `mappend` (False <$ eb))

-- TODO: generalize 'maybeR' & 'flipFlop'.  Perhaps using 'Monoid'.
-- Note that Nothing and (Any False) are mempty.

-- | Count occurrences of an event.  See also 'countE'.
countR :: (Ord t, Num n) => Event' t a -> Reactive' t n
countR e = 0 `stepper` countE_ e

-- | Tracing of reactive values
traceR :: (a -> String) -> Unop (Reactive' t a)
traceR shw (a `Stepper` e) = a `Stepper` traceE shw e