reflex-0.7.1.1: src/Reflex/Pure.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE PolyKinds #-}
#ifdef USE_REFLEX_OPTIMIZER
{-# OPTIONS_GHC -fplugin=Reflex.Optimizer #-}
#endif
-- There are two expected orphan instances in this module:
-- * MonadSample (Pure t) ((->) t)
-- * MonadHold (Pure t) ((->) t)
{-# OPTIONS_GHC -fno-warn-orphans #-}
-- |
-- Module: Reflex.Pure
-- Description:
-- This module provides a pure implementation of Reflex, which is intended to
-- serve as a reference for the semantics of the Reflex class. All
-- implementations of Reflex should produce the same results as this
-- implementation, although performance and laziness/strictness may differ.
module Reflex.Pure
( Pure
, Behavior (..)
, Event (..)
, Dynamic (..)
, Incremental (..)
) where
import Control.Monad
import Data.Dependent.Map (DMap)
import Data.GADT.Compare (GCompare)
import qualified Data.Dependent.Map as DMap
import Data.IntMap (IntMap)
import qualified Data.IntMap as IntMap
import Data.Maybe
import Data.MemoTrie
import Data.Monoid
import Data.Type.Coercion
import Reflex.Class
import Data.Kind (Type)
-- | A completely pure-functional 'Reflex' timeline, identifying moments in time
-- with the type @/t/@.
data Pure (t :: Type)
-- | The 'Enum' instance of @/t/@ must be dense: for all @/x :: t/@, there must not exist
-- any @/y :: t/@ such that @/'pred' x < y < x/@. The 'HasTrie' instance will be used
-- exclusively to memoize functions of @/t/@, not for any of its other capabilities.
instance (Enum t, HasTrie t, Ord t) => Reflex (Pure t) where
newtype Behavior (Pure t) a = Behavior { unBehavior :: t -> a }
newtype Event (Pure t) a = Event { unEvent :: t -> Maybe a }
newtype Dynamic (Pure t) a = Dynamic { unDynamic :: t -> (a, Maybe a) }
newtype Incremental (Pure t) p = Incremental { unIncremental :: t -> (PatchTarget p, Maybe p) }
type PushM (Pure t) = (->) t
type PullM (Pure t) = (->) t
never :: Event (Pure t) a
never = Event $ \_ -> Nothing
constant :: a -> Behavior (Pure t) a
constant x = Behavior $ \_ -> x
push :: (a -> PushM (Pure t) (Maybe b)) -> Event (Pure t) a -> Event (Pure t) b
push f e = Event $ memo $ \t -> unEvent e t >>= \o -> f o t
pushCheap :: (a -> PushM (Pure t) (Maybe b)) -> Event (Pure t) a -> Event (Pure t) b
pushCheap = push
pull :: PullM (Pure t) a -> Behavior (Pure t) a
pull = Behavior . memo
-- [UNUSED_CONSTRAINT]: The following type signature for merge will produce a
-- warning because the GCompare instance is not used; however, removing the
-- GCompare instance produces a different warning, due to that constraint
-- being present in the original class definition.
--mergeG :: GCompare k => (forall a. q a -> Event (Pure t) (v a))
-- -> DMap k q -> Event (Pure t) (DMap k v)
mergeG nt events = Event $ memo $ \t ->
let currentOccurrences = DMap.mapMaybeWithKey (\_ q -> case nt q of Event a -> a t) events
in if DMap.null currentOccurrences
then Nothing
else Just currentOccurrences
-- The instance signature doeesn't compile, leave commented for documentation
-- fanG :: GCompare k => Event (Pure t) (DMap k v) -> EventSelectorG (Pure t) k v
fanG e = EventSelectorG $ \k -> Event $ \t -> unEvent e t >>= DMap.lookup k
switch :: Behavior (Pure t) (Event (Pure t) a) -> Event (Pure t) a
switch b = Event $ memo $ \t -> unEvent (unBehavior b t) t
coincidence :: Event (Pure t) (Event (Pure t) a) -> Event (Pure t) a
coincidence e = Event $ memo $ \t -> unEvent e t >>= \o -> unEvent o t
current :: Dynamic (Pure t) a -> Behavior (Pure t) a
current d = Behavior $ \t -> fst $ unDynamic d t
updated :: Dynamic (Pure t) a -> Event (Pure t) a
updated d = Event $ \t -> snd $ unDynamic d t
unsafeBuildDynamic :: PullM (Pure t) a -> Event (Pure t) a -> Dynamic (Pure t) a
unsafeBuildDynamic readV0 v' = Dynamic $ \t -> (readV0 t, unEvent v' t)
-- See UNUSED_CONSTRAINT, above.
--unsafeBuildIncremental :: Patch p => PullM (Pure t) a -> Event (Pure t) (p
--a) -> Incremental (Pure t) p a
unsafeBuildIncremental readV0 p = Incremental $ \t -> (readV0 t, unEvent p t)
mergeIncrementalG = mergeIncrementalImpl
mergeIncrementalWithMoveG = mergeIncrementalImpl
currentIncremental i = Behavior $ \t -> fst $ unIncremental i t
updatedIncremental i = Event $ \t -> snd $ unIncremental i t
incrementalToDynamic i = Dynamic $ \t ->
let (old, mPatch) = unIncremental i t
e = case mPatch of
Nothing -> Nothing
Just patch -> apply patch old
in (old, e)
behaviorCoercion Coercion = Coercion
eventCoercion Coercion = Coercion
dynamicCoercion Coercion = Coercion
incrementalCoercion Coercion Coercion = Coercion
fanInt e = EventSelectorInt $ \k -> Event $ \t -> unEvent e t >>= IntMap.lookup k
mergeIntIncremental = mergeIntIncrementalImpl
mergeIncrementalImpl :: (PatchTarget p ~ DMap k q, GCompare k)
=> (forall a. q a -> Event (Pure t) (v a))
-> Incremental (Pure t) p -> Event (Pure t) (DMap k v)
mergeIncrementalImpl nt i = Event $ \t ->
let results = DMap.mapMaybeWithKey (\_ q -> case nt q of Event e -> e t) $ fst $ unIncremental i t
in if DMap.null results
then Nothing
else Just results
mergeIntIncrementalImpl :: (PatchTarget p ~ IntMap (Event (Pure t) a)) => Incremental (Pure t) p -> Event (Pure t) (IntMap a)
mergeIntIncrementalImpl i = Event $ \t ->
let results = IntMap.mapMaybeWithKey (\_ (Event e) -> e t) $ fst $ unIncremental i t
in if IntMap.null results
then Nothing
else Just results
instance Functor (Dynamic (Pure t)) where
fmap f d = Dynamic $ \t -> let (cur, upd) = unDynamic d t
in (f cur, fmap f upd)
instance Applicative (Dynamic (Pure t)) where
pure a = Dynamic $ \_ -> (a, Nothing)
(<*>) = ap
instance Monad (Dynamic (Pure t)) where
return = pure
(x :: Dynamic (Pure t) a) >>= (f :: a -> Dynamic (Pure t) b) = Dynamic $ \t ->
let (curX :: a, updX :: Maybe a) = unDynamic x t
(cur :: b, updOuter :: Maybe b) = unDynamic (f curX) t
(updInner :: Maybe b, updBoth :: Maybe b) = case updX of
Nothing -> (Nothing, Nothing)
Just nextX -> let (c, u) = unDynamic (f nextX) t
in (Just c, u)
in (cur, getFirst $ mconcat $ map First [updBoth, updOuter, updInner])
instance MonadSample (Pure t) ((->) t) where
sample :: Behavior (Pure t) a -> (t -> a)
sample = unBehavior
instance (Enum t, HasTrie t, Ord t) => MonadHold (Pure t) ((->) t) where
hold :: a -> Event (Pure t) a -> t -> Behavior (Pure t) a
hold initialValue e initialTime = Behavior f
where f = memo $ \sampleTime ->
-- Really, the sampleTime should never be prior to the initialTime,
-- because that would mean the Behavior is being sampled before
-- being created.
if sampleTime <= initialTime
then initialValue
else let lastTime = pred sampleTime
in fromMaybe (f lastTime) $ unEvent e lastTime
holdDyn v0 = buildDynamic (return v0)
buildDynamic :: (t -> a) -> Event (Pure t) a -> t -> Dynamic (Pure t) a
buildDynamic initialValue e initialTime =
let Behavior f = hold (initialValue initialTime) e initialTime
in Dynamic $ \t -> (f t, unEvent e t)
holdIncremental :: Patch p => PatchTarget p -> Event (Pure t) p -> t -> Incremental (Pure t) p
holdIncremental initialValue e initialTime = Incremental $ \t -> (f t, unEvent e t)
where f = memo $ \sampleTime ->
-- Really, the sampleTime should never be prior to the initialTime,
-- because that would mean the Behavior is being sampled before
-- being created.
if sampleTime <= initialTime
then initialValue
else let lastTime = pred sampleTime
lastValue = f lastTime
in case unEvent e lastTime of
Nothing -> lastValue
Just x -> fromMaybe lastValue $ apply x lastValue
headE = slowHeadE