packages feed

bearriver-0.14.5: src/FRP/BearRiver/Event.hs

{-# LANGUAGE CPP #-}
-- |
-- Module      : FRP.BearRiver.Event
-- Copyright   : (c) Ivan Perez, 2014-2022
--               (c) George Giorgidze, 2007-2012
--               (c) Henrik Nilsson, 2005-2006
--               (c) Antony Courtney and Henrik Nilsson, Yale University, 2003-2004
-- License     : BSD3
--
-- Maintainer  : ivan.perez@keera.co.uk
-- Stability   : provisional
-- Portability : portable
--
-- Events in BearRiver represent discrete time-signals, meaning those that do
-- not change continuously. Examples of event-carrying signals would be mouse
-- clicks (in between clicks it is assumed that there is no click), some
-- keyboard events, button presses on wiimotes or window-manager events.
--
-- The type 'Event' is isomorphic to 'Maybe' (@Event a = NoEvent | Event a@)
-- but, semantically, a 'Maybe'-carrying signal could change continuously,
-- whereas an 'Event'-carrying signal should not: for two events in subsequent
-- samples, there should be an small enough sampling frequency such that we
-- sample between those two samples and there are no 'Event's between them.
-- Nevertheless, no mechanism in Yampa will check this or misbehave if this
-- assumption is violated.
--
-- Events are essential for many other BearRiver constructs, like switches (see
-- 'FRP.BearRiver.Switches.switch' for details).
module FRP.BearRiver.Event
    (
      -- * The Event type
      Event(..)
    , noEvent
    , noEventFst
    , noEventSnd

      -- * Utility functions similar to those available for Maybe
    , event
    , fromEvent
    , isEvent
    , isNoEvent

      -- * Event tagging
    , tag
    , tagWith
    , attach

      -- * Event merging (disjunction) and joining (conjunction)
    , lMerge
    , rMerge
    , merge
    , mergeBy
    , mapMerge
    , mergeEvents
    , catEvents
    , joinE
    , splitE

      -- * Event filtering
    , filterE
    , mapFilterE
    , gate

      -- * Utilities for easy event construction
    , maybeToEvent

    )
  where

-- External imports
#if !MIN_VERSION_base(4,8,0)
import           Control.Applicative (Applicative (..), (<$>))
#endif
import           Control.Applicative (Alternative (..))
import           Control.DeepSeq     (NFData (..))
import qualified Control.Monad.Fail  as Fail

infixl 8 `tag`, `attach`, `gate`
infixl 7 `joinE`
infixl 6 `lMerge`, `rMerge`, `merge`

-- * The Event type

-- | A single possible event occurrence, that is, a value that may or may not
-- occur. Events are used to represent values that are not produced
-- continuously, such as mouse clicks (only produced when the mouse is clicked,
-- as opposed to mouse positions, which are always defined).
data Event a = Event a | NoEvent
  deriving (Eq, Ord, Show)

-- | Make the NoEvent constructor available. Useful e.g. for initialization,
-- ((-->) & friends), and it's easily available anyway (e.g. mergeEvents []).
noEvent :: Event a
noEvent = NoEvent

-- | Suppress any event in the first component of a pair.
noEventFst :: (Event a, b) -> (Event c, b)
noEventFst (_, b) = (NoEvent, b)

-- | Suppress any event in the second component of a pair.
noEventSnd :: (a, Event b) -> (a, Event c)
noEventSnd (a, _) = (a, NoEvent)


-- | Functor instance (could be derived).
instance Functor Event where
  -- | Apply function to value carried by 'Event', if any.
  fmap _ NoEvent   = NoEvent
  fmap f (Event c) = Event (f c)

-- | Applicative instance (similar to 'Maybe').
instance Applicative Event where
  -- | Wrap a pure value in an 'Event'.
  pure = Event
  -- | If any value (function or arg) is 'NoEvent', everything is.
  Event f <*> Event x = Event (f x)
  _       <*> _       = NoEvent

-- | Monad instance.
instance Monad Event where
  -- | Combine events, return 'NoEvent' if any value in the sequence is
  -- 'NoEvent'.
  Event x >>= f = f x
  NoEvent >>= _ = NoEvent

  -- | See 'pure'.
  return = pure

-- | MonadFail instance
instance Fail.MonadFail Event where
  -- | Fail with 'NoEvent'.
  fail _ = NoEvent

-- | Alternative instance.
instance Alternative Event where
  -- | An empty alternative carries no event, so it is ignored.
  empty = NoEvent
  -- | Merge favouring the left event ('NoEvent' only if both are 'NoEvent').
  NoEvent <|> r = r
  l       <|> _ = l

-- | NFData instance.
instance NFData a => NFData (Event a) where
  -- | Evaluate value carried by event.
  rnf NoEvent   = ()
  rnf (Event a) = rnf a `seq` ()

-- * Utility functions similar to those available for Maybe

-- | An event-based version of the maybe function.
event :: a -> (b -> a) -> Event b -> a
event _ f (Event x) = f x
event x _ NoEvent   = x

-- | Extract the value from an event. Fails if there is no event.
fromEvent :: Event a -> a
fromEvent (Event x) = x
fromEvent _         = error "fromEvent NoEvent"

-- | Tests whether the input represents an actual event.
isEvent :: Event a -> Bool
isEvent (Event _) = True
isEvent _         = False

-- | Negation of 'isEvent'.
isNoEvent :: Event a -> Bool
isNoEvent = not . isEvent

-- * Event tagging

-- | Tags an (occurring) event with a value ("replacing" the old value).
--
-- Applicative-based definition: tag = ($>)
tag :: Event a -> b -> Event b
e `tag` b = fmap (const b) e

-- | Tags an (occurring) event with a value ("replacing" the old value). Same as
-- 'tag' with the arguments swapped.
--
-- Applicative-based definition: tagWith = (<$)
tagWith :: b -> Event a -> Event b
tagWith = flip tag

-- | Attaches an extra value to the value of an occurring event.
attach :: Event a -> b -> Event (a, b)
e `attach` b = fmap (\a -> (a, b)) e

-- * Event merging (disjunction) and joining (conjunction)

-- | Left-biased event merge (always prefer left event, if present).
lMerge :: Event a -> Event a -> Event a
lMerge = (<|>)

-- | Right-biased event merge (always prefer right event, if present).
rMerge :: Event a -> Event a -> Event a
rMerge = flip (<|>)

-- | Unbiased event merge: simultaneous occurrence is an error.
merge :: Event a -> Event a -> Event a
merge = mergeBy $ error "Bearriver: merge: Simultaneous event occurrence."

-- | Event merge parameterized by a conflict resolution function.
--
-- Applicative-based definition:
-- mergeBy f le re = (f <$> le <*> re) <|> le <|> re
mergeBy :: (a -> a -> a) -> Event a -> Event a -> Event a
mergeBy _       NoEvent      NoEvent      = NoEvent
mergeBy _       le@(Event _) NoEvent      = le
mergeBy _       NoEvent      re@(Event _) = re
mergeBy resolve (Event l)    (Event r)    = Event (resolve l r)

-- | A generic event merge-map utility that maps event occurrences, merging the
-- results. The first three arguments are mapping functions, the third of which
-- will only be used when both events are present. Therefore, 'mergeBy' =
-- 'mapMerge' 'id' 'id'.
--
-- Applicative-based definition:
-- mapMerge lf rf lrf le re = (f <$> le <*> re) <|> (lf <$> le) <|> (rf <$> re)
mapMerge :: (a -> c)
            -- ^ Mapping function used when first event is present.
         -> (b -> c)
            -- ^ Mapping function used when second event is present.
         -> (a -> b -> c)
            -- ^ Mapping function used when both events are present.
         -> Event a
            -- ^ First event
         -> Event b
            -- ^ Second event
         -> Event c
mapMerge _  _  _   NoEvent   NoEvent   = NoEvent
mapMerge lf _  _   (Event l) NoEvent   = Event (lf l)
mapMerge _  rf _   NoEvent   (Event r) = Event (rf r)
mapMerge _  _  lrf (Event l) (Event r) = Event (lrf l r)

-- | Merge a list of events; foremost event has priority.
--
-- Foldable-based definition:
-- mergeEvents :: Foldable t => t (Event a) -> Event a
-- mergeEvents =  asum
mergeEvents :: [Event a] -> Event a
mergeEvents = foldr lMerge NoEvent

-- | Collect simultaneous event occurrences; no event if none.
catEvents :: [Event a] -> Event [a]
catEvents eas = case [ a | Event a <- eas ] of
                  [] -> NoEvent
                  as -> Event as

-- | Join (conjunction) of two events. Only produces an event if both events
-- exist.
--
-- Applicative-based definition:
-- joinE = liftA2 (,)
joinE :: Event a -> Event b -> Event (a, b)
joinE NoEvent   _         = NoEvent
joinE _         NoEvent   = NoEvent
joinE (Event l) (Event r) = Event (l, r)

-- | Split event carrying pairs into two events.
splitE :: Event (a, b) -> (Event a, Event b)
splitE NoEvent        = (NoEvent, NoEvent)
splitE (Event (a, b)) = (Event a, Event b)

-- * Event filtering

-- | Filter out events that don't satisfy some predicate.
filterE :: (a -> Bool) -> Event a -> Event a
filterE p e@(Event a) = if p a then e else NoEvent
filterE _ NoEvent     = NoEvent

-- | Combined event mapping and filtering. Note: since 'Event' is a 'Functor',
-- see 'fmap' for a simpler version of this function with no filtering.
mapFilterE :: (a -> Maybe b) -> Event a -> Event b
mapFilterE f e = e >>= (maybeToEvent . f)

-- | Enable/disable event occurrences based on an external condition.
gate :: Event a -> Bool -> Event a
_ `gate` False = NoEvent
e `gate` True  = e

-- * Utilities for easy event construction

-- | Convert a maybe value into a event ('Event' is isomorphic to 'Maybe').
maybeToEvent :: Maybe a -> Event a
maybeToEvent Nothing  = NoEvent
maybeToEvent (Just a) = Event a