on develop help sign in
Massive documentation and naming cleanup

* Add haddocks for everything * Deprecate lots of things * Reorganize some things

commit7050e5a85c73022a1efe4d4f9d1bd9d494e67af6 authorRyan Trinkle <ryan.trinkle@gmail.com> dateMon 1 Aug 2016, 11:55 (10 years ago) parent95dbb74
13 files changed+909−461
src/Data/Functor/Misc.hs+103−33 src/Data/WeakBag.hs+42−5 src/Reflex.hs+2 src/Reflex/Class.hs+235−87 src/Reflex/Dynamic.hs+277−192 src/Reflex/Dynamic/Nub.hs+44−6 src/Reflex/Dynamic/TH.hs+20−4 src/Reflex/Host/Class.hs+82−37 src/Reflex/Spider.hs+2 src/Reflex/Spider/Internal.hs+56−57 test/Reflex/Bench/Focused.hs+12−12 test/Reflex/Pure.hs+19−13 test/Reflex/Test/Micro.hs+15−15
src/Data/Functor/Misc.hs+103−33
@@ -8,7 +8,27 @@
88{-# LANGUAGE RankNTypes #-}
99{-# LANGUAGE ScopedTypeVariables #-}
1010{-# LANGUAGE StandaloneDeriving #-}
11−module Data.Functor.Misc where
11+-- | This module provides types and functions with no particular theme, but
12+-- which are relevant to the use of 'Functor'-based datastructures like
13+-- 'Data.Dependent.Map.DMap'.
14+module Data.Functor.Misc
15+ ( -- * Const2
16+ Const2 (..)
17+ , dmapToMap
18+ , mapToDMap
19+ -- * WrapArg
20+ , WrapArg (..)
21+ -- * Convenience functions for DMap
22+ , mapWithFunctorToDMap
23+ , combineDMapsWithKey
24+ -- * Deprecated functions
25+ , sequenceDmap
26+ , wrapDMap
27+ , rewrapDMap
28+ , unwrapDMap
29+ , unwrapDMapMaybe
30+ , extractFunctorDMap
31+ ) where
1232
1333import Control.Applicative ((<$>))
1434import Control.Monad.Identity
@@ -22,19 +42,13 @@ import qualified Data.Map as Map
2242import Data.These
2343import Data.Typeable hiding (Refl)
2444
25−data WrapArg :: (k -> *) -> (k -> *) -> * -> * where
26− WrapArg :: f a -> WrapArg g f (g a)
27−
28−instance GEq f => GEq (WrapArg g f) where
29− geq (WrapArg a) (WrapArg b) = (\Refl -> Refl) <$> geq a b
30−
31−instance GCompare f => GCompare (WrapArg g f) where
32− gcompare (WrapArg a) (WrapArg b) = case gcompare a b of
33− GLT -> GLT
34− GEQ -> GEQ
35− GGT -> GGT
45+--------------------------------------------------------------------------------
46+-- Const2
47+--------------------------------------------------------------------------------
3648
37−data Const2 :: * -> * -> * -> * where
49+-- | 'Const2' stores a value of a given type 'k' and ensures that a particular
50+-- type 'v' is always given for the last type parameter
51+data Const2 :: * -> x -> x -> * where
3852 Const2 :: k -> Const2 k v v
3953 deriving (Typeable)
4054
@@ -58,13 +72,57 @@ instance Ord k => GCompare (Const2 k v) where
5872 EQ -> GEQ
5973 GT -> GGT
6074
61−{-# INLINE sequenceDmap #-}
62−sequenceDmap :: (Monad m, GCompare f) => DMap f m -> m (DMap f Identity)
63−sequenceDmap = DMap.foldrWithKey (\k mv mx -> mx >>= \x -> mv >>= \v -> return $ DMap.insert k (Identity v) x)
64− (return DMap.empty)
75+-- | Convert a 'DMap' to a regular 'Map'
76+dmapToMap :: DMap (Const2 k v) Identity -> Map k v
77+dmapToMap = Map.fromDistinctAscList . map (\(Const2 k :=> Identity v) -> (k, v)) . DMap.toAscList
78+
79+-- | Convert a regular 'Map' to a 'DMap'
80+mapToDMap :: Map k v -> DMap (Const2 k v) Identity
81+mapToDMap = DMap.fromDistinctAscList . map (\(k, v) -> Const2 k :=> Identity v) . Map.toAscList
82+
83+-- | Convert a regular 'Map', where the values are already wrapped in a functor,
84+-- to a 'DMap'
85+mapWithFunctorToDMap :: Map k (f v) -> DMap (Const2 k v) f
86+mapWithFunctorToDMap = DMap.fromDistinctAscList . map (\(k, v) -> Const2 k :=> v) . Map.toAscList
87+
88+--------------------------------------------------------------------------------
89+-- WrapArg
90+--------------------------------------------------------------------------------
91+
92+-- | 'WrapArg' can be used to tag a value in one functor with a type
93+-- representing another functor. This was primarily used with dependent-map <
94+-- 0.2, in which the value type was not wrapped in a separate functor.
95+data WrapArg :: (k -> *) -> (k -> *) -> * -> * where
96+ WrapArg :: f a -> WrapArg g f (g a)
97+
98+instance GEq f => GEq (WrapArg g f) where
99+ geq (WrapArg a) (WrapArg b) = (\Refl -> Refl) <$> geq a b
100+
101+instance GCompare f => GCompare (WrapArg g f) where
102+ gcompare (WrapArg a) (WrapArg b) = case gcompare a b of
103+ GLT -> GLT
104+ GEQ -> GEQ
105+ GGT -> GGT
106+
107+--------------------------------------------------------------------------------
108+-- Convenience functions for DMap
109+--------------------------------------------------------------------------------
110+
111+-- | Map over all key/value pairs in a 'DMap', potentially altering the key as
112+-- well as the value. The provided function MUST preserve the ordering of the
113+-- keys, or the resulting 'DMap' will be malformed.
114+mapKeyValuePairsMonotonic :: (DSum k v -> DSum k' v') -> DMap k v -> DMap k' v'
115+mapKeyValuePairsMonotonic f = DMap.fromDistinctAscList . map f . DMap.toAscList
65116
66117{-# INLINE combineDMapsWithKey #-}
67−combineDMapsWithKey :: forall f g h i. GCompare f => (forall (a :: *). f a -> These (g a) (h a) -> i a) -> DMap f g -> DMap f h -> DMap f i
118+-- | Union two 'DMap's of different types, yielding another type. Each key that
119+-- is present in either input map will be present in the output.
120+combineDMapsWithKey :: forall f g h i.
121+ GCompare f
122+ => (forall a. f a -> These (g a) (h a) -> i a)
123+ -> DMap f g
124+ -> DMap f h
125+ -> DMap f i
68126combineDMapsWithKey f mg mh = DMap.fromList $ go (DMap.toList mg) (DMap.toList mh)
69127 where go :: [DSum f g] -> [DSum f h] -> [DSum f i]
70128 go [] hs = map (\(hk :=> hv) -> hk :=> f hk (That hv)) hs
@@ -74,26 +132,38 @@ combineDMapsWithKey f mg mh = DMap.fromList $ go (DMap.toList mg) (DMap.toList m
74132 GEQ -> (gk :=> f gk (These gv hv)) : go gs' hs'
75133 GGT -> (hk :=> f hk (That hv)) : go gs hs'
76134
135+--------------------------------------------------------------------------------
136+-- Deprecated functions
137+--------------------------------------------------------------------------------
138+
139+{-# INLINE sequenceDmap #-}
140+{-# DEPRECATED sequenceDmap "Use 'Data.Dependent.Map.traverseWithKey (\\_ t -> fmap Identity t)' instead" #-}
141+-- | Run the actions contained in the 'DMap'
142+sequenceDmap :: Applicative t => DMap f t -> t (DMap f Identity)
143+sequenceDmap = DMap.traverseWithKey $ \_ t -> Identity <$> t
144+
145+{-# DEPRECATED wrapDMap "Use 'Data.Dependent.Map.map (f . runIdentity)' instead" #-}
146+-- | Replace the 'Identity' functor for a 'DMap''s values with a different functor
77147wrapDMap :: (forall a. a -> f a) -> DMap k Identity -> DMap k f
78−wrapDMap f = DMap.mapWithKey $ \_ -> f . runIdentity
148+wrapDMap f = DMap.map $ f . runIdentity
79149
150+{-# DEPRECATED rewrapDMap "Use 'Data.Dependent.Map.map' instead" #-}
151+-- | Replace one functor for a 'DMap''s values with a different functor
80152rewrapDMap :: (forall (a :: *). f a -> g a) -> DMap k f -> DMap k g
81−rewrapDMap f = DMap.mapWithKey $ \_ -> f
153+rewrapDMap = DMap.map
82154
155+{-# DEPRECATED unwrapDMap "Use 'Data.Dependent.Map.map (Identity . f)' instead" #-}
156+-- | Replace one functor for a 'DMap''s values with the 'Identity' functor
83157unwrapDMap :: (forall a. f a -> a) -> DMap k f -> DMap k Identity
84−unwrapDMap f = DMap.mapWithKey $ \_ -> Identity . f
85−
86−unwrapDMapMaybe :: (forall a. f a -> Maybe a) -> DMap k f -> DMap k Identity
87−unwrapDMapMaybe f m = DMap.fromDistinctAscList [k :=> Identity w | (k :=> v) <- DMap.toAscList m, Just w <- [f v]]
158+unwrapDMap f = DMap.map $ Identity . f
88159
89−mapToDMap :: Map k v -> DMap (Const2 k v) Identity
90−mapToDMap = DMap.fromDistinctAscList . map (\(k, v) -> Const2 k :=> Identity v) . Map.toAscList
91−
92−mapWithFunctorToDMap :: Map k (f v) -> DMap (Const2 k v) f
93−mapWithFunctorToDMap = DMap.fromDistinctAscList . map (\(k, v) -> Const2 k :=> v) . Map.toAscList
160+{-# DEPRECATED unwrapDMapMaybe "Use 'Data.Dependent.Map.mapMaybeWithKey (\\_ a -> fmap Identity $ f a)' instead" #-}
161+-- | Like 'unwrapDMap', but possibly delete some values from the DMap
162+unwrapDMapMaybe :: GCompare k => (forall a. f a -> Maybe a) -> DMap k f -> DMap k Identity
163+unwrapDMapMaybe f = DMap.mapMaybeWithKey $ \_ a -> Identity <$> f a
94164
165+{-# DEPRECATED extractFunctorDMap "Use 'mapKeyValuePairsMonotonic (\\(Const2 k :=> Identity v) -> Const2 k :=> v)' instead" #-}
166+-- | Eliminate the 'Identity' functor in a 'DMap' and replace it with the
167+-- underlying functor
95168extractFunctorDMap :: DMap (Const2 k (f v)) Identity -> DMap (Const2 k v) f
96−extractFunctorDMap = DMap.fromDistinctAscList . map (\(Const2 k :=> Identity v) -> Const2 k :=> v) . DMap.toAscList
97−
98−dmapToMap :: DMap (Const2 k v) Identity -> Map k v
99−dmapToMap = Map.fromDistinctAscList . map (\(Const2 k :=> Identity v) -> (k, v)) . DMap.toAscList
169+extractFunctorDMap = mapKeyValuePairsMonotonic $ \(Const2 k :=> Identity v) -> Const2 k :=> v
src/Data/WeakBag.hs+42−5
@@ -1,4 +1,16 @@
1−module Data.WeakBag (WeakBag, WeakBagTicket, empty, singleton, insert, Data.WeakBag.traverse, remove) where
1+-- | This module defines the 'WeakBag' type, which represents a mutable
2+-- collection of items that does not cause the items to be retained in memory.
3+-- This is useful for situations where a value needs to be inspected or modified
4+-- if it is still alive, but can be ignored if it is dead.
5+module Data.WeakBag
6+ ( WeakBag
7+ , WeakBagTicket
8+ , empty
9+ , singleton
10+ , insert
11+ , traverse
12+ , remove
13+ ) where
214
315import Control.Concurrent.STM
416import Control.Exception
@@ -10,20 +22,37 @@ import qualified Data.IntMap.Strict as IntMap
1022import Data.IORef
1123import System.Mem.Weak
1224
13−import Prelude hiding (mapM_)
25+import Prelude hiding (mapM_, traverse)
1426
27+-- | A @WeakBag a@ holds a set of values of type @a@, but does not retain them -
28+-- that is, they can still be garbage-collected. As long as the @a@s remain
29+-- alive, the 'WeakBag' will continue to refer to them.
1530data WeakBag a = WeakBag
1631 { _weakBag_nextId :: {-# UNPACK #-} !(TVar Int) --TODO: what if this wraps around?
1732 , _weakBag_children :: {-# UNPACK #-} !(TVar (IntMap (Weak a)))
1833 }
1934
35+-- | When inserting an item into a 'WeakBag', a 'WeakBagTicket' is returned. If
36+-- the caller retains the ticket, the item is guranteed to stay in memory (and
37+-- thus in the 'WeakBag'). The ticket can also be used to remove the item from
38+-- the 'WeakBag' prematurely (i.e. while it is still alive), using 'remove'.
2039data WeakBagTicket a = WeakBagTicket
2140 { _weakBagTicket_weakItem :: {-# UNPACK #-} !(Weak a)
2241 , _weakBagTicket_item :: {-# NOUNPACK #-} !a
2342 }
2443
44+-- | Insert an item into a 'WeakBag'.
2545{-# INLINE insert #-}
26−insert :: a -> WeakBag a -> IORef (Weak b) -> (b -> IO ()) -> IO (WeakBagTicket a)
46+insert :: a -- ^ The item
47+ -> WeakBag a -- ^ The 'WeakBag' to insert into
48+ -> IORef (Weak b) -- ^ An arbitrary value to be used in the following
49+ -- callback
50+ -> (b -> IO ()) -- ^ A callback to be invoked when the item is removed
51+ -- (whether automatically by the item being garbage
52+ -- collected or manually via 'remove')
53+ -> IO (WeakBagTicket a) -- ^ Returns a 'WeakBagTicket' that ensures the
54+ -- item is retained and allows the item to be
55+ -- removed.
2756insert a (WeakBag nextId children) wbRef finalizer = do
2857 a' <- evaluate a
2958 wbRef' <- evaluate wbRef
@@ -50,6 +79,7 @@ insert a (WeakBag nextId children) wbRef finalizer = do
5079 , _weakBagTicket_item = a'
5180 }
5281
82+-- | Create an empty 'WeakBag'.
5383{-# INLINE empty #-}
5484empty :: IO (WeakBag a)
5585empty = do
@@ -61,6 +91,8 @@ empty = do
6191 }
6292 return bag
6393
94+-- | Create a 'WeakBag' with one item; equivalent to creating the 'WeakBag' with
95+-- 'empty', then using 'insert'.
6496{-# INLINE singleton #-}
6597singleton :: a -> IORef (Weak b) -> (b -> IO ()) -> IO (WeakBag a, WeakBagTicket a)
6698singleton a wbRef finalizer = do
@@ -69,8 +101,10 @@ singleton a wbRef finalizer = do
69101 return (bag, ticket)
70102
71103{-# INLINE traverse #-}
72−-- | Visit every node in the given list. If new nodes are appended during the traversal, they will not be visited.
73−-- Every live node that was in the list when the traversal began will be visited exactly once; however, no guarantee is made about the order of the traversal.
104+-- | Visit every node in the given list. If new nodes are appended during the
105+-- traversal, they will not be visited. Every live node that was in the list
106+-- when the traversal began will be visited exactly once; however, no guarantee
107+-- is made about the order of the traversal.
74108traverse :: MonadIO m => WeakBag a -> (a -> m ()) -> m ()
75109traverse (WeakBag _ children) f = do
76110 cs <- liftIO $ readTVarIO children
@@ -78,6 +112,9 @@ traverse (WeakBag _ children) f = do
78112 ma <- liftIO $ deRefWeak c
79113 mapM_ f ma
80114
115+-- | Remove an item from the 'WeakBag'; does nothing if invoked multiple times
116+-- on the same 'WeakBagTicket'.
81117{-# INLINE remove #-}
82118remove :: WeakBagTicket a -> IO ()
83119remove = finalize . _weakBagTicket_weakItem
120+--TODO: Should 'remove' also drop the reference to the item?
src/Reflex.hs+2
@@ -1,3 +1,5 @@
1+-- | This module exports all of the commonly-used functionality of Reflex; if
2+-- you are just getting started with Reflex, this is probably what you want.
13module Reflex (module X) where
24
35import Reflex.Class as X
src/Reflex/Class.hs+235−87
@@ -1,4 +1,3 @@
1−
21{-# LANGUAGE CPP #-}
32{-# LANGUAGE FlexibleContexts #-}
43{-# LANGUAGE FlexibleInstances #-}
@@ -14,7 +13,71 @@
1413{-# LANGUAGE TypeOperators #-}
1514{-# LANGUAGE UndecidableInstances #-}
1615
17−module Reflex.Class where
16+-- | This module contains the Reflex interface, as well as a variety of
17+-- convenience functions for working with 'Event's, 'Behavior's, and other
18+-- signals.
19+module Reflex.Class
20+ ( -- * Primitives
21+ Reflex (..)
22+ , MonadSample (..)
23+ , MonadHold (..)
24+ -- ** 'fan'-related types
25+ , EventSelector (..)
26+ -- ** 'Incremental'-related types
27+ , Patch (..)
28+ , PatchDMap (..)
29+ -- * Convenience functions
30+ , constDyn
31+ , pushAlways
32+ -- ** Combining 'Event's
33+ , leftmost
34+ , mergeMap
35+ , mergeList
36+ , mergeWith
37+ -- ** Breaking up 'Event's
38+ , splitE
39+ , fanMap
40+ , dmapToThese
41+ , EitherTag (..)
42+ , eitherToDSum
43+ , dsumToEither
44+ -- ** Switching between 'Event's
45+ , switchPromptly
46+ -- ** Using 'Event's to sample 'Behavior's
47+ , tag
48+ , attach
49+ , attachWith
50+ , attachWithMaybe
51+ -- ** Blocking an 'Event' based on a 'Behavior'
52+ , gate
53+ -- ** Combining 'Dynamic's
54+ , distributeDMapOverDynPure
55+ , zipDyn
56+ , zipDynWith
57+ -- ** Accumulating state
58+ , Accumulator (..)
59+ , mapAccum_
60+ , mapAccumM_
61+ , mapAccumMaybeM_
62+ , zipListWithEvent
63+ , headE
64+ , tailE
65+ , switcher
66+ -- * Debugging functions
67+ , traceEvent
68+ , traceEventWith
69+ -- * Unsafe functions
70+ , unsafeDynamic
71+ -- * 'FunctorMaybe'
72+ , FunctorMaybe (..)
73+ , fforMaybe
74+ , ffilter
75+ -- * Miscellaneous convenience functions
76+ , ffor
77+ -- * Deprecated functions
78+ , onceE
79+ , appendEvents
80+ ) where
1881
1982import Control.Applicative
2083import Control.Monad.Identity hiding (forM, forM_, mapM, mapM_, sequence, sequence_)
@@ -52,54 +115,76 @@ import Prelude hiding (foldl, mapM, mapM_, sequence, sequence_)
52115
53116import Debug.Trace (trace)
54117
118+-- | The 'Reflex' class contains all the primitive functionality needed for
119+-- Functional Reactive Programming (FRP). The @t@ type parameter indicates
120+-- which "timeline" is in use. Timelines are fully-independent FRP contexts,
121+-- and the type of the timeline determines the FRP engine to be used. For most
122+-- purposes, the 'Reflex.Spider' implementation is recommended.
55123class ( MonadHold t (PushM t)
56124 , MonadSample t (PullM t)
57125 , MonadFix (PushM t)
58126 , Functor (Event t)
59127 , Functor (Behavior t)
60128 , Functor (Dynamic t)
61− , Applicative (Dynamic t) -- Necessary for GHC <=7.8
129+ , Applicative (Dynamic t) -- Necessary for GHC <= 7.8
62130 , Monad (Dynamic t)
63131 ) => Reflex t where
64− -- | A container for a value that can change over time. Behaviors can be sampled at will, but it is not possible to be notified when they change
132+ -- | A container for a value that can change over time. 'Behavior's can be
133+ -- sampled at will, but it is not possible to be notified when they change
65134 data Behavior t :: * -> *
66− -- | A stream of occurrences. During any given frame, an Event is either occurring or not occurring; if it is occurring, it will contain a value of the given type (its "occurrence type")
135+ -- | A stream of occurrences. During any given frame, an 'Event' is either
136+ -- occurring or not occurring; if it is occurring, it will contain a value of
137+ -- the given type (its "occurrence type")
67138 data Event t :: * -> *
68− -- | A container for a value that can change over time and allows notifications on changes.
69− -- Basically a combination of a 'Behavior' and an 'Event', with a rule that the Behavior will
70− -- change if and only if the Event fires.
139+ -- | A container for a value that can change over time and allows
140+ -- notifications on changes. Basically a combination of a 'Behavior' and an
141+ -- 'Event', with a rule that the 'Behavior' will change if and only if the
142+ -- 'Event' fires.
71143 data Dynamic t :: * -> *
72− -- | A container for a value that can change over time and allows notifications on changes.
73− -- Basically a combination of a 'Behavior' and an 'Event', with a rule that the Behavior will
74− -- change if and only if the Event fires.
144+ -- | A container for a value that can change over time and allows
145+ -- notifications on changes. Basically a combination of a 'Behavior' and an
146+ -- 'Event', with a rule that the 'Behavior' will change if and only if the
147+ -- 'Event' fires.
75148 data Incremental t :: (* -> *) -> * -> *
76− -- | An Event with no occurrences
149+ -- | An 'Event' with no occurrences
77150 never :: Event t a
78− -- | Create a Behavior that always has the given value
79− constant :: a -> Behavior t a --TODO: Refactor to use 'pure' from Applicative instead; however, we need to make sure that encouraging Applicative-style use of Behaviors doesn't have a negative performance impact
80− -- | Create an Event from another Event; the provided function can sample Behaviors and hold Events, and use the results to produce a occurring (Just) or non-occurring (Nothing) result
151+ -- | Create a 'Behavior' that always has the given value
152+ constant :: a -> Behavior t a --TODO: Refactor to use 'pure' from Applicative instead; however, we need to make sure that encouraging Applicative-style use of 'Behavior's doesn't have a negative performance impact
153+ -- | Create an 'Event' from another 'Event'; the provided function can sample
154+ -- 'Behavior's and hold 'Event's, and use the results to produce a occurring
155+ -- (Just) or non-occurring (Nothing) result
81156 push :: (a -> PushM t (Maybe b)) -> Event t a -> Event t b
82157 -- | A monad for doing complex push-based calculations efficiently
83158 type PushM t :: * -> *
84− -- | Create a Behavior by reading from other Behaviors; the result will be recomputed whenever any of the read Behaviors changes
159+ -- | Create a 'Behavior' by reading from other 'Behavior's; the result will be
160+ -- recomputed whenever any of the read 'Behavior's changes
85161 pull :: PullM t a -> Behavior t a
86162 -- | A monad for doing complex pull-based calculations efficiently
87163 type PullM t :: * -> *
88− -- | Merge a collection of events; the resulting Event will only occur if at least one input event is occuring, and will contain all of the input keys that are occurring simultaneously
164+ -- | Merge a collection of events; the resulting 'Event' will only occur if at
165+ -- least one input event is occuring, and will contain all of the input keys
166+ -- that are occurring simultaneously
89167 merge :: GCompare k => DMap k (Event t) -> Event t (DMap k Identity) --TODO: Generalize to get rid of DMap use --TODO: Provide a type-level guarantee that the result is not empty
90− -- | Efficiently fan-out an event to many destinations. This function should be partially applied, and then the result applied repeatedly to create child events
168+ -- | Efficiently fan-out an event to many destinations. This function should
169+ -- be partially applied, and then the result applied repeatedly to create
170+ -- child events
91171 fan :: GCompare k => Event t (DMap k Identity) -> EventSelector t k --TODO: Can we help enforce the partial application discipline here? The combinator is worthless without it
92− -- | Create an Event that will occur whenever the currently-selected input Event occurs
172+ -- | Create an 'Event' that will occur whenever the currently-selected input
173+ -- 'Event' occurs
93174 switch :: Behavior t (Event t a) -> Event t a
94− -- | Create an Event that will occur whenever the input event is occurring and its occurrence value, another Event, is also occurring
175+ -- | Create an 'Event' that will occur whenever the input event is occurring
176+ -- and its occurrence value, another 'Event', is also occurring
95177 coincidence :: Event t (Event t a) -> Event t a
96178 -- | Extract the 'Behavior' of a 'Dynamic'.
97179 current :: Dynamic t a -> Behavior t a
98180 -- | Extract the 'Event' of the 'Dynamic'.
99181 updated :: Dynamic t a -> Event t a
100− -- | Create a new 'Dynamic'. The given PullM must always return the most recent firing of the given Event, if any.
182+ -- | Create a new 'Dynamic'. The given 'PullM' must always return the most
183+ -- recent firing of the given 'Event', if any.
101184 unsafeBuildDynamic :: PullM t a -> Event t a -> Dynamic t a
102− -- | Create a new 'Incremental'. The given PullM's value must always change in the same way that the accumulated application of patches would change that value.
185+ -- | Create a new 'Incremental'. The given 'PullM''s value must always change
186+ -- in the same way that the accumulated application of patches would change
187+ -- that value.
103188 unsafeBuildIncremental :: Patch p => PullM t a -> Event t (p a) -> Incremental t p a
104189 -- | Create a merge whose parents can change over time
105190 mergeIncremental :: GCompare k => Incremental t PatchDMap (DMap k (Event t)) -> Event t (DMap k Identity)
@@ -110,12 +195,16 @@ class ( MonadHold t (PushM t)
110195 -- | Convert an 'Incremental' to a 'Dynamic'
111196 incrementalToDynamic :: Patch p => Incremental t p a -> Dynamic t a
112197
198+-- | A 'Patch' type represents a kind of change made to a datastructure.
113199class Patch p where
114− apply :: p a -> a -> Maybe a -- Return Nothing if no change is necessary
200+ -- | Apply the patch @p a@ to the value @a@. If no change is needed, return
201+ -- 'Nothing'.
202+ apply :: p a -> a -> Maybe a
115203
116204instance Patch Identity where
117205 apply (Identity a) _ = Just a
118206
207+-- | A set of changes to a 'DMap'. Only DMap types are allowed for @a@.
119208data PatchDMap a where
120209 PatchDMap :: GCompare k => DMap k (Compose Maybe v) -> PatchDMap (DMap k v)
121210
@@ -143,27 +232,55 @@ instance Patch PatchDMap where
143232 Nothing -> Just $ Constant ()
144233 Just _ -> Nothing
145234
235+-- | Construct a 'Dynamic' from a 'Behavior' and an 'Event'. The 'Behavior'
236+-- _must_ change when and only when the 'Event' fires, such that the
237+-- 'Behavior''s value is always equal to the most recent firing of the 'Event';
238+-- if this is not the case, the resulting 'Dynamic' will behave
239+-- nondeterministically.
146240unsafeDynamic :: Reflex t => Behavior t a -> Event t a -> Dynamic t a
147241unsafeDynamic = unsafeBuildDynamic . sample
148242
243+-- | Construct a 'Dynamic' value that never changes
149244constDyn :: Reflex t => a -> Dynamic t a
150245constDyn = pure
151246
247+-- | 'MonadSample' designates monads that can read the current value of a
248+-- 'Behavior'. This includes both 'PullM' and 'PushM'.
152249class (Applicative m, Monad m) => MonadSample t m | m -> t where
153− -- | Get the current value in the Behavior
250+ -- | Get the current value in the 'Behavior'
154251 sample :: Behavior t a -> m a
155252
253+-- | 'MonadHold' designates monads that can create new 'Behavior's based on
254+-- 'Event's; usually this will be 'PushM' or a monad based on it. 'MonadHold'
255+-- is required to create any stateful computations with Reflex.
156256class MonadSample t m => MonadHold t m where
157− -- | Create a new Behavior whose value will initially be equal to the given value and will be updated whenever the given Event occurs. The update takes effect immediately after the Event occurs; if the occurrence that sets the Behavior (or one that is simultaneous with it) is used to sample the Behavior, it will see the *old* value of the Behavior, not the new one.
257+ -- | Create a new 'Behavior' whose value will initially be equal to the given
258+ -- value and will be updated whenever the given 'Event' occurs. The update
259+ -- takes effect immediately after the 'Event' occurs; if the occurrence that
260+ -- sets the 'Behavior' (or one that is simultaneous with it) is used to sample
261+ -- the 'Behavior', it will see the _old_ value of the 'Behavior', not the new
262+ -- one.
158263 hold :: a -> Event t a -> m (Behavior t a)
159264 -- | Create a 'Dynamic' value using the given initial value that changes every
160265 -- time the 'Event' occurs.
161266 holdDyn :: a -> Event t a -> m (Dynamic t a)
162− -- | Create an 'Incremental' value using the given initial value that changes every
163− -- time the 'Event' occurs.
267+ -- | Create an 'Incremental' value using the given initial value that changes
268+ -- every time the 'Event' occurs.
164269 holdIncremental :: Patch p => a -> Event t (p a) -> m (Incremental t p a)
165270
166−newtype EventSelector t k = EventSelector { select :: forall a. k a -> Event t a }
271+-- | An 'EventSelector' allows you to efficiently 'select' an 'Event' based on a
272+-- key. This is much more efficient than filtering for each key with
273+-- 'fmapMaybe'.
274+newtype EventSelector t k = EventSelector
275+ { -- | Retrieve the 'Event' for the given key. The type of the 'Event' is
276+ -- determined by the type of the key, so this can be used to fan-out
277+ -- 'Event's whose sub-'Event's have different types.
278+ --
279+ -- Using 'EventSelector's and the 'fan' primitive is far more efficient than
280+ -- (but equivalent to) using 'fmapMaybe' to select only the relevant
281+ -- occurrences of an 'Event'.
282+ select :: forall a. k a -> Event t a
283+ }
167284
168285--------------------------------------------------------------------------------
169286-- Instances
@@ -221,8 +338,8 @@ instance MonadHold t m => MonadHold t (ContT r m) where
221338-- Convenience functions
222339--------------------------------------------------------------------------------
223340
224−-- | Create an Event from another Event.
225−-- The provided function can sample 'Behavior's and hold 'Event's.
341+-- | Create an 'Event' from another 'Event'. The provided function can sample
342+-- 'Behavior's and hold 'Event's.
226343pushAlways :: Reflex t => (a -> PushM t b) -> Event t a -> Event t b
227344pushAlways f = push (fmap Just . f)
228345
@@ -255,7 +372,7 @@ instance (Reflex t, IsString a) => IsString (Behavior t a) where
255372
256373instance Reflex t => Monad (Behavior t) where
257374 a >>= f = pull $ sample a >>= sample . f
258− -- Note: it is tempting to write (_ >> b = b); however, this would result in (fail x >> return y) succeeding (returning y), which violates the law that (a >> b = a >>= \_ -> b), since the implementation of (>>=) above actually will fail. Since we can't examine Behaviors other than by using sample, I don't think it's possible to write (>>) to be more efficient than the (>>=) above.
375+ -- Note: it is tempting to write (_ >> b = b); however, this would result in (fail x >> return y) succeeding (returning y), which violates the law that (a >> b = a >>= \_ -> b), since the implementation of (>>=) above actually will fail. Since we can't examine 'Behavior's other than by using sample, I don't think it's possible to write (>>) to be more efficient than the (>>=) above.
259376 return = constant
260377 fail = error "Monad (Behavior t) does not support fail"
261378
@@ -305,11 +422,11 @@ ffilter f = fmapMaybe $ \x -> if f x then Just x else Nothing
305422instance Reflex t => Alt (Event t) where
306423 ev1 <!> ev2 = leftmost [ev1, ev2]
307424
308−-- | Event intersection (convenient interface to 'coincidence').
425+-- | 'Event' intersection (convenient interface to 'coincidence').
309426instance Reflex t => Apply (Event t) where
310427 evf <.> evx = coincidence (fmap (<$> evx) evf)
311428
312−-- | Event intersection (convenient interface to 'coincidence').
429+-- | 'Event' intersection (convenient interface to 'coincidence').
313430instance Reflex t => Bind (Event t) where
314431 evx >>- f = coincidence (f <$> evx)
315432 join = coincidence
@@ -329,27 +446,23 @@ instance Reflex t => Plus (Event t) where
329446tag :: Reflex t => Behavior t b -> Event t a -> Event t b
330447tag b = pushAlways $ \_ -> sample b
331448
332−-- | Create a new 'Event' that combines occurences of supplied 'Event'
333−-- with the current value of the 'Behavior'.
449+-- | Create a new 'Event' that combines occurences of supplied 'Event' with the
450+-- current value of the 'Behavior'.
334451attach :: Reflex t => Behavior t a -> Event t b -> Event t (a, b)
335452attach = attachWith (,)
336453
337−-- | Create a new 'Event' that occurs when the supplied 'Event' occurs
338−-- by combining it with the current value of the 'Behavior'.
454+-- | Create a new 'Event' that occurs when the supplied 'Event' occurs by
455+-- combining it with the current value of the 'Behavior'.
339456attachWith :: Reflex t => (a -> b -> c) -> Behavior t a -> Event t b -> Event t c
340457attachWith f = attachWithMaybe $ \a b -> Just $ f a b
341458
342−-- | Create a new 'Event' by combining each occurence with the current
343−-- value of the 'Behavior'. The occurrence is discarded if the combining function
344−-- returns Nothing
459+-- | Create a new 'Event' by combining each occurence with the current value of
460+-- the 'Behavior'. The occurrence is discarded if the combining function returns
461+-- Nothing
345462attachWithMaybe :: Reflex t => (a -> b -> Maybe c) -> Behavior t a -> Event t b -> Event t c
346463attachWithMaybe f b e = flip push e $ \o -> (`f` o) <$> sample b
347464
348−-- | Alias for 'headE'
349−onceE :: (Reflex t, MonadHold t m, MonadFix m) => Event t a -> m (Event t a)
350−onceE = headE
351−
352−-- | Create a new 'Event' that only occurs on the first occurence of
465+-- | Create a new 'Event' that only occurs only once, on the first occurence of
353466-- the supplied 'Event'.
354467headE :: (Reflex t, MonadHold t m, MonadFix m) => Event t a -> m (Event t a)
355468headE e = do
@@ -358,38 +471,38 @@ headE e = do
358471 e' `seq` return ()
359472 return e'
360473
361−-- | Create a new 'Event' that occurs on all but the first occurence
362−-- of the supplied 'Event'.
474+-- | Create a new 'Event' that occurs on all but the first occurence of the
475+-- supplied 'Event'.
363476tailE :: (Reflex t, MonadHold t m, MonadFix m) => Event t a -> m (Event t a)
364477tailE e = snd <$> headTailE e
365478
366−-- | Create a tuple of two 'Event's with the first one occuring only
367−-- the first time the supplied 'Event' occurs and the second occuring
368−-- on all but the first occurence.
479+-- | Create a tuple of two 'Event's with the first one occuring only the first
480+-- time the supplied 'Event' occurs and the second occuring on all but the first
481+-- occurence.
369482headTailE :: (Reflex t, MonadHold t m, MonadFix m) => Event t a -> m (Event t a, Event t a)
370483headTailE e = do
371484 eHead <- headE e
372485 be <- hold never $ fmap (const e) eHead
373486 return (eHead, switch be)
374487
375−-- | Split the supplied 'Event' into two individual 'Event's occuring
376−-- at the same time with the respective values from the tuple.
488+-- | Split the supplied 'Event' into two individual 'Event's occuring at the
489+-- same time with the respective values from the tuple.
377490splitE :: Reflex t => Event t (a, b) -> (Event t a, Event t b)
378491splitE e = (fmap fst e, fmap snd e)
379492
380493-- | Print the supplied 'String' and the value of the 'Event' on each
381494-- occurence. This should /only/ be used for debugging.
382495--
383−-- Note: As with Debug.Trace.trace, the message will only be printed if
384−-- the 'Event' is actually used.
496+-- Note: As with Debug.Trace.trace, the message will only be printed if the
497+-- 'Event' is actually used.
385498traceEvent :: (Reflex t, Show a) => String -> Event t a -> Event t a
386499traceEvent s = traceEventWith $ \x -> s <> ": " <> show x
387500
388−-- | Print the output of the supplied function on each occurence of
389−-- the 'Event'. This should /only/ be used for debugging.
501+-- | Print the output of the supplied function on each occurence of the
502+-- 'Event'. This should /only/ be used for debugging.
390503--
391−-- Note: As with Debug.Trace.trace, the message will only be printed if
392−-- the 'Event' is actually used.
504+-- Note: As with Debug.Trace.trace, the message will only be printed if the
505+-- 'Event' is actually used.
393506traceEventWith :: Reflex t => (a -> String) -> Event t a -> Event t a
394507traceEventWith f = push $ \x -> trace (f x) $ return $ Just x
395508
@@ -441,12 +554,6 @@ dmapToThese m = case (DMap.lookup LeftTag m, DMap.lookup RightTag m) of
441554 (Nothing, Just (Identity b)) -> Just $ That b
442555 (Just (Identity a), Just (Identity b)) -> Just $ These a b
443556
444−-- | Create a new 'Event' that occurs if at least one of the supplied
445−-- 'Event's occurs. If both occur at the same time they are combined
446−-- using 'mappend'.
447−appendEvents :: (Reflex t, Monoid a) => Event t a -> Event t a -> Event t a
448−appendEvents e1 e2 = mergeThese mappend <$> align e1 e2
449−
450557{-# DEPRECATED sequenceThese "Use bisequenceA or bisequence from the bifunctors package instead" #-}
451558{-# ANN sequenceThese "HLint: ignore Use fmap" #-}
452559sequenceThese :: Monad m => These (m a) (m b) -> m (These a b)
@@ -469,9 +576,9 @@ instance (Semigroup a, Reflex t) => Monoid (Event t a) where
469576 mappend = (<>)
470577 mconcat = fmap sconcat . mergeList
471578
472−-- | Create a new 'Event' that occurs if at least one of the 'Event's
473−-- in the list occurs. If multiple occur at the same time they are
474−-- folded from the left with the given function.
579+-- | Create a new 'Event' that occurs if at least one of the 'Event's in the
580+-- list occurs. If multiple occur at the same time they are folded from the left
581+-- with the given function.
475582mergeWith :: Reflex t => (a -> a -> a) -> [Event t a] -> Event t a
476583mergeWith f es = fmap (Prelude.foldl1 f . map (\(Const2 _ :=> Identity v) -> v) . DMap.toList)
477584 . merge
@@ -479,31 +586,32 @@ mergeWith f es = fmap (Prelude.foldl1 f . map (\(Const2 _ :=> Identity v) -> v)
479586 . map (\(k, v) -> Const2 k :=> v)
480587 $ zip [0 :: Int ..] es
481588
482−-- | Create a new 'Event' that occurs if at least one of the 'Event's
483−-- in the list occurs. If multiple occur at the same time the value is
484−-- the value of the leftmost event.
589+-- | Create a new 'Event' that occurs if at least one of the 'Event's in the
590+-- list occurs. If multiple occur at the same time the value is the value of the
591+-- leftmost event.
485592leftmost :: Reflex t => [Event t a] -> Event t a
486593leftmost = mergeWith const
487594
488−-- | Create a new 'Event' that occurs if at least one of the 'Event's
489−-- in the list occurs and has a list of the values of all 'Event's
490−-- occuring at that time.
595+-- | Create a new 'Event' that occurs if at least one of the 'Event's in the
596+-- list occurs and has a list of the values of all 'Event's occuring at that
597+-- time.
491598mergeList :: Reflex t => [Event t a] -> Event t (NonEmpty a)
492599mergeList [] = never
493600mergeList es = mergeWith (<>) $ map (fmap (:|[])) es
494601
495−-- | Create a new 'Event' combining the map of 'Event's into an
496−-- 'Event' that occurs if at least one of them occurs and has a map of
497−-- values of all 'Event's occuring at that time.
602+-- | Create a new 'Event' combining the map of 'Event's into an 'Event' that
603+-- occurs if at least one of them occurs and has a map of values of all 'Event's
604+-- occuring at that time.
498605mergeMap :: (Reflex t, Ord k) => Map k (Event t a) -> Event t (Map k a)
499606mergeMap = fmap dmapToMap . merge . mapWithFunctorToDMap
500607
501−-- | Split the event into an 'EventSelector' that allows efficient
502−-- selection of the individual 'Event's.
608+-- | Split the event into an 'EventSelector' that allows efficient selection of
609+-- the individual 'Event's.
503610fanMap :: (Reflex t, Ord k) => Event t (Map k a) -> EventSelector t (Const2 k a)
504611fanMap = fan . fmap mapToDMap
505612
506−-- | Switches to the new event whenever it receives one; the new event is used immediately, on the same frame that it is switched to
613+-- | Switches to the new event whenever it receives one; the new event is used
614+-- immediately, on the same frame that it is switched to
507615switchPromptly :: forall t m a. (Reflex t, MonadHold t m) => Event t a -> Event t (Event t a) -> m (Event t a)
508616switchPromptly ea0 eea = do
509617 bea <- hold ea0 eea
@@ -515,17 +623,24 @@ instance Reflex t => Align (Event t) where
515623 nil = never
516624 align ea eb = fmapMaybe dmapToThese $ merge $ DMap.fromList [LeftTag :=> ea, RightTag :=> eb]
517625
518−-- | Create a new 'Event' that only occurs if the supplied 'Event'
519−-- occurs and the 'Behavior' is true at the time of occurence.
626+-- | Create a new 'Event' that only occurs if the supplied 'Event' occurs and
627+-- the 'Behavior' is true at the time of occurence.
520628gate :: Reflex t => Behavior t Bool -> Event t a -> Event t a
521629gate = attachWithMaybe $ \allow a -> if allow then Just a else Nothing
522630
523−-- | Create a new behavior given a starting behavior and switch to a the
524−-- behvior carried by the event when it fires.
631+-- | Create a new behavior given a starting behavior and switch to a the behvior
632+-- carried by the event when it fires.
525633switcher :: (Reflex t, MonadHold t m)
526634 => Behavior t a -> Event t (Behavior t a) -> m (Behavior t a)
527635switcher b eb = pull . (sample <=< sample) <$> hold b eb
528636
637+-- | Combine two 'Dynamic's. The result will change whenever either (or both)
638+-- input 'Dynamic' changes. Equivalent to @zipDynWith (,)@.
639+zipDyn :: Reflex t => Dynamic t a -> Dynamic t b -> Dynamic t (a, b)
640+zipDyn = zipDynWith (,)
641+
642+-- | Combine two 'Dynamic's with a combining function. The result will change
643+-- whenever either (or both) input 'Dynamic' changes.
529644zipDynWith :: Reflex t => (a -> b -> c) -> Dynamic t a -> Dynamic t b -> Dynamic t c
530645zipDynWith f da db =
531646 let eab = align (updated da) (updated db)
@@ -546,18 +661,27 @@ instance (Reflex t, Monoid a) => Monoid (Dynamic t a) where
546661 mempty = constDyn mempty
547662 mappend = zipDynWith mappend
548663
664+-- | This function converts a 'DMap' whose elements are 'Dynamic's into a
665+-- 'Dynamic' 'DMap'. Its implementation is more efficient than doing the same
666+-- through the use of multiple uses of 'zipWithDyn' or 'Applicative' operators.
549667distributeDMapOverDynPure :: forall t k. (Reflex t, GCompare k) => DMap k (Dynamic t) -> Dynamic t (DMap k Identity)
550668distributeDMapOverDynPure dm = case DMap.toList dm of
551669 [] -> constDyn DMap.empty
552670 [k :=> v] -> fmap (DMap.singleton k . Identity) v
553671 _ ->
554672 let getInitial = DMap.traverseWithKey (\_ -> fmap Identity . sample . current) dm
555− edmPre = merge $ rewrapDMap updated dm
673+ edmPre = merge $ DMap.map updated dm
556674 result = unsafeBuildDynamic getInitial $ flip pushAlways edmPre $ \news -> do
557675 olds <- sample $ current result
558676 return $ DMap.unionWithKey (\_ _ new -> new) olds news
559677 in result
560678
679+--------------------------------------------------------------------------------
680+-- Accumulator
681+--------------------------------------------------------------------------------
682+
683+-- | An 'Accumulator' type can be built by accumulating occurrences of an
684+-- 'Event'.
561685class Reflex t => Accumulator t f | f -> t where
562686 accum :: (MonadHold t m, MonadFix m) => (a -> b -> a) -> a -> Event t b -> m (f a)
563687 accum f = accumMaybe $ \v o -> Just $ f v o
@@ -574,21 +698,30 @@ class Reflex t => Accumulator t f | f -> t where
574698 mapAccumMaybe f = mapAccumMaybeM $ \v o -> return $ f v o
575699 mapAccumMaybeM :: (MonadHold t m, MonadFix m) => (a -> b -> PushM t (Maybe a, Maybe c)) -> a -> Event t b -> m (f a, Event t c)
576700
701+-- | Accumulate occurrences of an 'Event', producing an output occurrence each
702+-- time. Discard the underlying 'Accumulator'.
577703mapAccum_ :: (Reflex t, MonadHold t m, MonadFix m) => (a -> b -> (a, c)) -> a -> Event t b -> m (Event t c)
578704mapAccum_ f z e = do
579705 (_ :: Behavior t a, result) <- mapAccum f z e
580706 return result
581707
708+-- | Accumulate occurrences of an 'Event', possibly producing an output
709+-- occurrence each time. Discard the underlying 'Accumulator'.
582710mapAccumMaybe_ :: (Reflex t, MonadHold t m, MonadFix m) => (a -> b -> (Maybe a, Maybe c)) -> a -> Event t b -> m (Event t c)
583711mapAccumMaybe_ f z e = do
584712 (_ :: Behavior t a, result) <- mapAccumMaybe f z e
585713 return result
586714
715+-- | Accumulate occurrences of an 'Event', using a 'PushM' action and producing
716+-- an output occurrence each time. Discard the underlying 'Accumulator'.
587717mapAccumM_ :: (Reflex t, MonadHold t m, MonadFix m) => (a -> b -> PushM t (a, c)) -> a -> Event t b -> m (Event t c)
588718mapAccumM_ f z e = do
589719 (_ :: Behavior t a, result) <- mapAccumM f z e
590720 return result
591721
722+-- | Accumulate occurrences of an 'Event', using a 'PushM' action and possibly
723+-- producing an output occurrence each time. Discard the underlying
724+-- 'Accumulator'.
592725mapAccumMaybeM_ :: (Reflex t, MonadHold t m, MonadFix m) => (a -> b -> PushM t (Maybe a, Maybe c)) -> a -> Event t b -> m (Event t c)
593726mapAccumMaybeM_ f z e = do
594727 (_ :: Behavior t a, result) <- mapAccumMaybeM f z e
@@ -619,12 +752,27 @@ instance Reflex t => Accumulator t (Event t) where
619752 accumMaybeM f z e = updated <$> accumMaybeM f z e
620753 mapAccumMaybeM f z e = first updated <$> mapAccumMaybeM f z e
621754
622−-- | Create a new 'Event' by combining each occurence with the next value
623−-- of the list using the supplied function. If the list runs out of items,
624−-- all subsequent 'Event' occurrences will be ignored.
755+-- | Create a new 'Event' by combining each occurence with the next value of the
756+-- list using the supplied function. If the list runs out of items, all
757+-- subsequent 'Event' occurrences will be ignored.
625758zipListWithEvent :: (Reflex t, MonadHold t m, MonadFix m) => (a -> b -> c) -> [a] -> Event t b -> m (Event t c)
626759zipListWithEvent f l e = do
627760 let f' a b = case a of
628761 h:t -> (Just t, Just $ f h b)
629762 _ -> (Nothing, Nothing) --TODO: Unsubscribe the event?
630763 mapAccumMaybe_ f' l e
764+
765+--------------------------------------------------------------------------------
766+-- Deprecated functions
767+--------------------------------------------------------------------------------
768+
769+-- | Create a new 'Event' that occurs if at least one of the supplied 'Event's
770+-- occurs. If both occur at the same time they are combined using 'mappend'.
771+{-# DEPRECATED appendEvents "If a 'Semigroup a' instance is available, use 'mappend'; otherwise, use 'alignWith (mergeThese mappend)' instead" #-}
772+appendEvents :: (Reflex t, Monoid a) => Event t a -> Event t a -> Event t a
773+appendEvents = alignWith $ mergeThese mappend
774+
775+-- | Alias for 'headE'
776+{-# DEPRECATED onceE "Use 'headE' instead" #-}
777+onceE :: (Reflex t, MonadHold t m, MonadFix m) => Event t a -> m (Event t a)
778+onceE = headE
src/Reflex/Dynamic.hs+277−192
@@ -12,46 +12,61 @@
1212{-# LANGUAGE TypeFamilies #-}
1313{-# LANGUAGE TypeOperators #-}
1414{-# LANGUAGE UndecidableInstances #-}
15−module Reflex.Dynamic ( Dynamic -- Abstract so we can preserve the law that the current value is always equal to the most recent update
16− , current
17− , updated
18− , constDyn
19− , holdDyn
20− , nubDyn
21− , count
22− , toggle
23− , switchPromptlyDyn
24− , tagDyn
25− , attachDyn
26− , attachDynWith
27− , attachDynWithMaybe
28− , mapDyn
29− , forDyn
30− , foldDyn
31− , foldDynM
32− , foldDynMaybe
33− , foldDynMaybeM
34− , combineDyn
35− , apDyn
36− , collectDyn
37− , mconcatDyn
38− , distributeDMapOverDyn
39− , joinDyn
40− , joinDynThroughMap
41− , traceDyn
42− , traceDynWith
43− , splitDyn
44− , splitDynPure
45− , Demux
46− , demux
47− , getDemuxed
48− -- Things that probably aren't very useful:
49− , HList (..)
50− , FHList (..)
51− , distributeFHListOverDyn
52− -- Unsafe
53− , unsafeDynamic
54− ) where
15+-- | This module contains various functions for working with 'Dynamic' values.
16+-- 'Dynamic' and its primitives have been moved to the 'Reflex' class.
17+module Reflex.Dynamic
18+ ( -- * Basics
19+ Dynamic -- Abstract so we can preserve the law that the current value is always equal to the most recent update
20+ , current
21+ , updated
22+ , holdDyn
23+ , constDyn
24+ , uniqDyn
25+ , uniqDynBy
26+ , count
27+ , toggle
28+ , switchPromptlyDyn
29+ , tagPromptlyDyn
30+ , attachPromptlyDyn
31+ , attachPromptlyDynWith
32+ , attachPromptlyDynWithMaybe
33+ , foldDyn
34+ , foldDynM
35+ , foldDynMaybe
36+ , foldDynMaybeM
37+ , joinDynThroughMap
38+ , traceDyn
39+ , traceDynWith
40+ , splitDynPure
41+ , distributeDMapOverDynPure
42+ , Demux
43+ , demux
44+ , demuxed
45+ -- Things that probably aren't very useful:
46+ , HList (..)
47+ , FHList (..)
48+ , collectDynPure
49+ , distributeFHListOverDynPure
50+ -- Unsafe
51+ , unsafeDynamic
52+ -- * Deprecated functions
53+ , apDyn
54+ , attachDyn
55+ , attachDynWith
56+ , attachDynWithMaybe
57+ , collectDyn
58+ , combineDyn
59+ , distributeDMapOverDyn
60+ , distributeFHListOverDyn
61+ , forDyn
62+ , getDemuxed
63+ , joinDyn
64+ , mapDyn
65+ , mconcatDyn
66+ , nubDyn
67+ , splitDyn
68+ , tagDyn
69+ ) where
5570
5671import Prelude hiding (mapM, mapM_)
5772
@@ -73,67 +88,41 @@ import Data.These
7388
7489import Debug.Trace
7590
76−data HList (l::[*]) where
77− HNil :: HList '[]
78− HCons :: e -> HList l -> HList (e ': l)
79−
80−infixr 2 `HCons`
81−
82−type family HRevApp (l1 :: [k]) (l2 :: [k]) :: [k]
83−type instance HRevApp '[] l = l
84−type instance HRevApp (e ': l) l' = HRevApp l (e ': l')
85−
86−hRevApp :: HList l1 -> HList l2 -> HList (HRevApp l1 l2)
87−hRevApp HNil l = l
88−hRevApp (HCons x l) l' = hRevApp l (HCons x l')
89−
90−hReverse :: HList l -> HList (HRevApp l '[])
91−hReverse l = hRevApp l HNil
92−
93−hBuild :: (HBuild' '[] r) => r
94−hBuild = hBuild' HNil
95−
96−class HBuild' l r where
97− hBuild' :: HList l -> r
98−
99−instance (l' ~ HRevApp l '[])
100− => HBuild' l (HList l') where
101− hBuild' = hReverse
102−
103−instance HBuild' (a ': l) r
104− => HBuild' l (a->r) where
105− hBuild' l x = hBuild' (HCons x l)
106−
107−-- | Create a new 'Dynamic' that only signals changes if the values
108−-- actually changed.
109−nubDyn :: (Reflex t, Eq a) => Dynamic t a -> Dynamic t a
110−nubDyn d =
111− let e' = attachWithMaybe (\x x' -> if x' == x then Nothing else Just x') (current d) (updated d)
112− in unsafeDynamic (current d) e' --TODO: Avoid invalidating the outgoing Behavior
113−
114−-- | Map a function over a 'Dynamic'.
115−mapDyn :: (Reflex t, Monad m) => (a -> b) -> Dynamic t a -> m (Dynamic t b)
116−mapDyn f = return . fmap f
117−
118−-- | Flipped version of 'mapDyn'.
119−forDyn :: (Reflex t, Monad m) => Dynamic t a -> (a -> b) -> m (Dynamic t b)
120−forDyn = flip mapDyn
121−
122−-- | Create a 'Dynamic' using the initial value and change it each
123−-- time the 'Event' occurs using a folding function on the previous
124−-- value and the value of the 'Event'.
91+-- | Create a new 'Dynamic' that only signals changes if the values actually
92+-- changed.
93+uniqDyn :: (Reflex t, Eq a) => Dynamic t a -> Dynamic t a
94+uniqDyn = uniqDynBy (==)
95+
96+-- | Create a new 'Dynamic' that changes only when the underlying 'Dynamic'
97+-- changes and the given function returns 'False' when given both the old and
98+-- the new values.
99+uniqDynBy :: Reflex t => (a -> a -> Bool) -> Dynamic t a -> Dynamic t a
100+uniqDynBy eq d =
101+ let e' = attachWithMaybe (\x x' -> if x' `eq` x then Nothing else Just x') (current d) (updated d)
102+ in unsafeDynamic (current d) e'
103+
104+-- | Create a 'Dynamic' using the initial value and change it each time the
105+-- 'Event' occurs using a folding function on the previous value and the value
106+-- of the 'Event'.
125107foldDyn :: (Reflex t, MonadHold t m, MonadFix m) => (a -> b -> b) -> b -> Event t a -> m (Dynamic t b)
126108foldDyn = accum . flip
127109
128−-- | Create a 'Dynamic' using the initial value and change it each
129−-- time the 'Event' occurs using a monadic folding function on the
130−-- previous value and the value of the 'Event'.
110+-- | Like 'foldDyn', but the combining function is a 'PushM' action, so it
111+-- can 'sample' existing 'Behaviors' and 'hold' new ones.
131112foldDynM :: (Reflex t, MonadHold t m, MonadFix m) => (a -> b -> PushM t b) -> b -> Event t a -> m (Dynamic t b)
132113foldDynM = accumM . flip
133114
115+-- | Create a 'Dynamic' using the provided initial value and change it each time
116+-- the provided 'Event' occurs, using a function to combine the old value with
117+-- the 'Event''s value. If the function returns 'Nothing', the value is not
118+-- changed; this is distinct from returning 'Just' the old value, since the
119+-- 'Dynamic''s 'updated' 'Event' will fire in the 'Just' case, and will not fire
120+-- in the 'Nothing' case.
134121foldDynMaybe :: (Reflex t, MonadHold t m, MonadFix m) => (a -> b -> Maybe b) -> b -> Event t a -> m (Dynamic t b)
135122foldDynMaybe = accumMaybe . flip
136123
124+-- | Like 'foldDynMaybe', but the combining function is a 'PushM' action, so it
125+-- can 'sample' existing 'Behaviors' and 'hold' new ones.
137126foldDynMaybeM :: (Reflex t, MonadHold t m, MonadFix m) => (a -> b -> PushM t (Maybe b)) -> b -> Event t a -> m (Dynamic t b)
138127foldDynMaybeM = accumMaybeM . flip
139128
@@ -146,78 +135,30 @@ count e = holdDyn 0 =<< zipListWithEvent const (iterate (+1) 1) e
146135toggle :: (Reflex t, MonadHold t m, MonadFix m) => Bool -> Event t a -> m (Dynamic t Bool)
147136toggle = foldDyn (const not)
148137
149−-- | Switches to the new 'Event' whenever it receives one. Switching
150−-- occurs *before* the inner 'Event' fires - so if the 'Dynamic' changes and both the old and new
151−-- inner Events fire simultaneously, the output will fire with the value of the *new* 'Event'.
138+-- | Switches to the new 'Event' whenever it receives one. Switching occurs
139+-- *before* the inner 'Event' fires - so if the 'Dynamic' changes and both the
140+-- old and new inner Events fire simultaneously, the output will fire with the
141+-- value of the *new* 'Event'.
152142switchPromptlyDyn :: forall t a. Reflex t => Dynamic t (Event t a) -> Event t a
153143switchPromptlyDyn de =
154144 let eLag = switch $ current de
155145 eCoincidences = coincidence $ updated de
156146 in leftmost [eCoincidences, eLag]
157147
158−{-
159−
160−mergeEventsWith :: Reflex t m => (a -> a -> a) -> Event t a -> Event t a -> m (Event t a)
161−mergeEventsWith f ea eb = mapE (mergeThese f) =<< alignEvents ea eb
162−
163−firstE :: (Reflex t m) => [Event t a] -> m (Event t a)
164−firstE [] = return never
165−firstE (h:t) = mergeEventsLeftBiased h =<< firstE t
166−
167−concatEventsWith :: (Reflex t m) => (a -> a -> a) -> [Event t a] -> m (Event t a)
168−concatEventsWith _ [] = return never
169−concatEventsWith _ [e] = return e
170−concatEventsWith f es = mapEM (fmap (foldl1 f . map (\(Const2 _ :=> v) -> v) . DMap.toList) . sequenceDmap) <=< mergeEventDMap $ DMap.fromList $ map (\(k, v) -> WrapArg (Const2 k) :=> v) $ zip [0 :: Int ..] es
171−--concatEventsWith f (h:t) = mergeEventsWith f h =<< concatEventsWith f t
172−
173−mconcatE :: (Reflex t m, Monoid a) => [Event t a] -> m (Event t a)
174−mconcatE = concatEventsWith mappend
175−-}
176−
177−-- | Split the 'Dynamic' into two 'Dynamic's, each taking the
178−-- respective value of the tuple.
179−splitDyn :: (Reflex t, Monad m) => Dynamic t (a, b) -> m (Dynamic t a, Dynamic t b)
180−splitDyn = return . splitDynPure
181−
148+-- | Split a 'Dynamic' pair into a pair of 'Dynamic's
182149splitDynPure :: Reflex t => Dynamic t (a, b) -> (Dynamic t a, Dynamic t b)
183150splitDynPure d = (fmap fst d, fmap snd d)
184151
185−-- | Merge the 'Dynamic' values using their 'Monoid' instance.
186−mconcatDyn :: forall t m a. (Reflex t, Monad m, Monoid a) => [Dynamic t a] -> m (Dynamic t a)
187−mconcatDyn = return . mconcat
188−
189−-- | Create a 'Dynamic' with a 'DMap' of values out of a 'DMap' of
190−-- Dynamic values.
191−distributeDMapOverDyn :: (Reflex t, Monad m, GCompare k) => DMap k (Dynamic t) -> m (Dynamic t (DMap k Identity))
192−distributeDMapOverDyn = return . distributeDMapOverDynPure
193−
152+-- | Create a 'Dynamic' with a 'DMap' of values out of a 'DMap' of 'Dynamic'
153+-- values. The resulting 'Dynamic' will change whenever one of the input
154+-- 'Dynamic's changes.
155+--
156+-- This function's implementation is more efficient than simply using
157+-- 'zipDynWith' or applicative syntax to achieve the same result, especially for
158+-- maps with many elements.
194159distributeMapOverDynPure :: (Reflex t, Ord k) => Map k (Dynamic t v) -> Dynamic t (Map k v)
195160distributeMapOverDynPure = fmap dmapToMap . distributeDMapOverDynPure . mapWithFunctorToDMap
196161
197−-- | Merge two 'Dynamic's into a new one using the provided
198−-- function. The new 'Dynamic' changes its value each time one of the
199−-- original 'Dynamic's changes its value.
200−combineDyn :: forall t m a b c. (Reflex t, Monad m) => (a -> b -> c) -> Dynamic t a -> Dynamic t b -> m (Dynamic t c)
201−combineDyn f da db = return $ zipDynWith f da db
202−
203−-- | A psuedo applicative version of ap for 'Dynamic'. Example useage:
204−--
205−-- > do
206−-- > person <- Person `mapDyn` dynFirstName
207−-- > `apDyn` dynListName
208−-- > `apDyn` dynAge
209−-- > `apDyn` dynAddress
210−apDyn :: forall t m a b. (Reflex t, Monad m)
211− => m (Dynamic t (a -> b))
212− -> Dynamic t a
213− -> m (Dynamic t b)
214−apDyn m a = do
215− r <- m
216− return $ r <*> a
217−
218−joinDyn :: Reflex t => Dynamic t (Dynamic t a) -> Dynamic t a
219−joinDyn = join
220−
221162--TODO: Generalize this to functors other than Maps
222163-- | Combine a 'Dynamic' of a 'Map' of 'Dynamic's into a 'Dynamic'
223164-- with the current values of the 'Dynamic's in a map.
@@ -249,37 +190,38 @@ traceDynWith f d =
249190-- | Replace the value of the 'Event' with the current value of the 'Dynamic'
250191-- each time the 'Event' occurs.
251192--
252−-- Note: `tagDyn d e` differs from `tag (current d) e` in the case that `e` is firing
253−-- at the same time that `d` is changing. With `tagDyn d e`, the *new* value of `d`
193+-- Note: `tagPromptlyDyn d e` differs from `tag (current d) e` in the case that `e` is firing
194+-- at the same time that `d` is changing. With `tagPromptlyDyn d e`, the *new* value of `d`
254195-- will replace the value of `e`, whereas with `tag (current d) e`, the *old* value
255196-- will be used, since the 'Behavior' won't be updated until the end of the frame.
256197-- Additionally, this means that the output 'Event' may not be used to directly change
257198-- the input 'Dynamic', because that would mean its value depends on itself. When creating
258199-- cyclic data flows, generally `tag (current d) e` is preferred.
259−tagDyn :: Reflex t => Dynamic t a -> Event t b -> Event t a
260−tagDyn = attachDynWith const
200+tagPromptlyDyn :: Reflex t => Dynamic t a -> Event t b -> Event t a
201+tagPromptlyDyn = attachPromptlyDynWith const
261202
262203-- | Attach the current value of the 'Dynamic' to the value of the
263204-- 'Event' each time it occurs.
264205--
265−-- Note: `attachDyn d` is not the same as `attach (current d)`. See 'tagDyn' for details.
266−attachDyn :: Reflex t => Dynamic t a -> Event t b -> Event t (a, b)
267−attachDyn = attachDynWith (,)
206+-- Note: `attachPromptlyDyn d` is not the same as `attach (current d)`. See 'tagPromptlyDyn' for details.
207+attachPromptlyDyn :: Reflex t => Dynamic t a -> Event t b -> Event t (a, b)
208+attachPromptlyDyn = attachPromptlyDynWith (,)
268209
269210-- | Combine the current value of the 'Dynamic' with the value of the
270211-- 'Event' each time it occurs.
271212--
272−-- Note: `attachDynWith f d` is not the same as `attachWith f (current d)`. See 'tagDyn' for details.
273−attachDynWith :: Reflex t => (a -> b -> c) -> Dynamic t a -> Event t b -> Event t c
274−attachDynWith f = attachDynWithMaybe $ \a b -> Just $ f a b
213+-- Note: `attachPromptlyDynWith f d` is not the same as `attachWith f (current d)`. See 'tagPromptlyDyn' for details.
214+attachPromptlyDynWith :: Reflex t => (a -> b -> c) -> Dynamic t a -> Event t b -> Event t c
215+attachPromptlyDynWith f = attachPromptlyDynWithMaybe $ \a b -> Just $ f a b
275216
276−-- | Create a new 'Event' by combining the value at each occurence
277−-- with the current value of the 'Dynamic' value and possibly
278−-- filtering if the combining function returns 'Nothing'.
217+-- | Create a new 'Event' by combining the value at each occurence with the
218+-- current value of the 'Dynamic' value and possibly filtering if the combining
219+-- function returns 'Nothing'.
279220--
280−-- Note: `attachDynWithMaybe f d` is not the same as `attachWithMaybe f (current d)`. See 'tagDyn' for details.
281−attachDynWithMaybe :: Reflex t => (a -> b -> Maybe c) -> Dynamic t a -> Event t b -> Event t c
282−attachDynWithMaybe f d e =
221+-- Note: `attachPromptlyDynWithMaybe f d` is not the same as `attachWithMaybe f
222+-- (current d)`. See 'tagPromptlyDyn' for details.
223+attachPromptlyDynWithMaybe :: Reflex t => (a -> b -> Maybe c) -> Dynamic t a -> Event t b -> Event t c
224+attachPromptlyDynWithMaybe f d e =
283225 let e' = attach (current d) e
284226 in fforMaybe (align e' $ updated d) $ \case
285227 This (a, b) -> f a b -- Only the tagging event is firing, so use that
@@ -290,19 +232,23 @@ attachDynWithMaybe f d e =
290232-- Demux
291233--------------------------------------------------------------------------------
292234
293−-- | Represents a time changing value together with an 'EventSelector'
294−-- that can efficiently detect when the underlying Dynamic has a particular value.
295−-- This is useful for representing data like the current selection of a long list.
235+-- | Represents a time changing value together with an 'EventSelector' that can
236+-- efficiently detect when the underlying 'Dynamic' has a particular value.
237+-- This is useful for representing data like the current selection of a long
238+-- list.
296239--
297240-- Semantically,
298−-- > getDemuxed (demux d) k === mapDyn (== k) d
299−-- However, the when getDemuxed is used multiple times, the complexity is only /O(log(n))/,
300−-- rather than /O(n)/ for mapDyn.
241+--
242+-- > demuxed (demux d) k === fmap (== k) d
243+--
244+-- However, the when getDemuxed is used multiple times, the complexity is only
245+-- /O(log(n))/, rather than /O(n)/ for fmap.
301246data Demux t k = Demux { demuxValue :: Behavior t k
302247 , demuxSelector :: EventSelector t (Const2 k Bool)
303248 }
304249
305−-- | Demultiplex an input value to a 'Demux' with many outputs. At any given time, whichever output is indicated by the given 'Dynamic' will be 'True'.
250+-- | Demultiplex an input value to a 'Demux' with many outputs. At any given
251+-- time, whichever output is indicated by the given 'Dynamic' will be 'True'.
306252demux :: (Reflex t, Ord k) => Dynamic t k -> Demux t k
307253demux k = Demux (current k)
308254 (fan $ attachWith (\k0 k1 -> if k0 == k1
@@ -311,12 +257,9 @@ demux k = Demux (current k)
311257 Const2 k1 :=> Identity True])
312258 (current k) (updated k))
313259
314−--TODO: The pattern of using hold (sample b0) can be reused in various places as a safe way of building certain kinds of Dynamics; see if we can factor this out
315−-- | Select a particular output of the 'Demux'; this is equivalent to (but much faster than)
316−-- mapping over the original 'Dynamic' and checking whether it is equal to the given key.
317−getDemuxed :: (Reflex t, Monad m, Eq k) => Demux t k -> k -> m (Dynamic t Bool)
318−getDemuxed d k = return $ demuxed d k
319−
260+-- | Select a particular output of the 'Demux'; this is equivalent to (but much
261+-- faster than) mapping over the original 'Dynamic' and checking whether it is
262+-- equal to the given key.
320263demuxed :: (Reflex t, Eq k) => Demux t k -> k -> Dynamic t Bool
321264demuxed d k =
322265 let e = select (demuxSelector d) (Const2 k)
@@ -328,6 +271,41 @@ demuxed d k =
328271
329272--TODO: This whole section is badly in need of cleanup
330273
274+-- | A heterogeneous list whose type and length are fixed statically. This is
275+-- reproduced from the 'HList' package due to integration issues, and because
276+-- very little other functionality from that library is needed.
277+data HList (l::[*]) where
278+ HNil :: HList '[]
279+ HCons :: e -> HList l -> HList (e ': l)
280+
281+infixr 2 `HCons`
282+
283+type family HRevApp (l1 :: [k]) (l2 :: [k]) :: [k]
284+type instance HRevApp '[] l = l
285+type instance HRevApp (e ': l) l' = HRevApp l (e ': l')
286+
287+hRevApp :: HList l1 -> HList l2 -> HList (HRevApp l1 l2)
288+hRevApp HNil l = l
289+hRevApp (HCons x l) l' = hRevApp l (HCons x l')
290+
291+hReverse :: HList l -> HList (HRevApp l '[])
292+hReverse l = hRevApp l HNil
293+
294+hBuild :: (HBuild' '[] r) => r
295+hBuild = hBuild' HNil
296+
297+class HBuild' l r where
298+ hBuild' :: HList l -> r
299+
300+instance (l' ~ HRevApp l '[])
301+ => HBuild' l (HList l') where
302+ hBuild' = hReverse
303+
304+instance HBuild' (a ': l) r
305+ => HBuild' l (a->r) where
306+ hBuild' l x = hBuild' (HCons x l)
307+
308+-- | Like 'HList', but with a functor wrapping each element.
331309data FHList f l where
332310 FHNil :: FHList f '[]
333311 FHCons :: f e -> FHList f l -> FHList f (e ': l)
@@ -378,9 +356,9 @@ instance RebuildSortedHList t => RebuildSortedHList (h ': t) where
378356dmapToHList :: forall l. RebuildSortedHList l => DMap (HListPtr l) Identity -> HList l
379357dmapToHList = rebuildSortedHList . DMap.toList
380358
381−distributeFHListOverDyn :: forall t m l. (Reflex t, Monad m, RebuildSortedHList l) => FHList (Dynamic t) l -> m (Dynamic t (HList l))
382−distributeFHListOverDyn = return . distributeFHListOverDynPure
383−
359+-- | Collect a hetereogeneous list whose elements are all 'Dynamic's into a
360+-- single 'Dynamic' whose value represents the current values of all of the
361+-- input 'Dynamic's.
384362distributeFHListOverDynPure :: (Reflex t, RebuildSortedHList l) => FHList (Dynamic t) l -> Dynamic t (HList l)
385363distributeFHListOverDynPure l = fmap dmapToHList $ distributeDMapOverDynPure $ fhlistToDMap l
386364
@@ -407,14 +385,9 @@ instance AllAreFunctors f t => AllAreFunctors f (h ': t) where
407385#endif
408386 fromFHList (a `FHCons` b) = a `HCons` fromFHList b
409387
410−collectDyn :: ( RebuildSortedHList (HListElems b)
411− , IsHList a, IsHList b
412− , AllAreFunctors (Dynamic t) (HListElems b)
413− , Reflex t, Monad m
414− , HListElems a ~ FunctorList (Dynamic t) (HListElems b)
415− ) => a -> m (Dynamic t b)
416−collectDyn = return . collectDynPure
417−
388+-- | Convert a datastructure whose constituent parts are all 'Dynamic's into a
389+-- single 'Dynamic' whose value represents all the current values of the input's
390+-- consitutent 'Dynamic's.
418391collectDynPure :: ( RebuildSortedHList (HListElems b)
419392 , IsHList a, IsHList b
420393 , AllAreFunctors (Dynamic t) (HListElems b)
@@ -455,3 +428,115 @@ instance IsHList (a, b, c, d, e, f) where
455428#if !defined(__GLASGOW_HASKELL__) || __GLASGOW_HASKELL__ < 800
456429 _ -> error "fromHList: impossible" -- Otherwise, GHC complains of a non-exhaustive pattern match; see https://ghc.haskell.org/trac/ghc/ticket/4139
457430#endif
431+
432+--------------------------------------------------------------------------------
433+-- Deprecated functions
434+--------------------------------------------------------------------------------
435+
436+-- | Map a function over a 'Dynamic'.
437+{-# DEPRECATED mapDyn "Use 'return . fmap f' instead of 'mapDyn f'; consider eliminating monadic style" #-}
438+mapDyn :: (Reflex t, Monad m) => (a -> b) -> Dynamic t a -> m (Dynamic t b)
439+mapDyn f = return . fmap f
440+
441+-- | Flipped version of 'mapDyn'.
442+{-# DEPRECATED forDyn "Use 'return . ffor a' instead of 'forDyn a'; consider eliminating monadic style" #-}
443+forDyn :: (Reflex t, Monad m) => Dynamic t a -> (a -> b) -> m (Dynamic t b)
444+forDyn a = return . ffor a
445+
446+-- | Split the 'Dynamic' into two 'Dynamic's, each taking the respective value
447+-- of the tuple.
448+{-# DEPRECATED splitDyn "Use 'return . splitDynPure' instead; consider eliminating monadic style" #-}
449+splitDyn :: (Reflex t, Monad m) => Dynamic t (a, b) -> m (Dynamic t a, Dynamic t b)
450+splitDyn = return . splitDynPure
451+
452+-- | Merge the 'Dynamic' values using their 'Monoid' instance.
453+{-# DEPRECATED mconcatDyn "Use 'return . mconcat' instead; consider eliminating monadic style" #-}
454+mconcatDyn :: forall t m a. (Reflex t, Monad m, Monoid a) => [Dynamic t a] -> m (Dynamic t a)
455+mconcatDyn = return . mconcat
456+
457+-- | This function no longer needs to be monadic; see 'distributeMapOverDynPure'.
458+{-# DEPRECATED distributeDMapOverDyn "Use 'return . distributeDMapOverDynPure' instead; consider eliminating monadic style" #-}
459+distributeDMapOverDyn :: (Reflex t, Monad m, GCompare k) => DMap k (Dynamic t) -> m (Dynamic t (DMap k Identity))
460+distributeDMapOverDyn = return . distributeDMapOverDynPure
461+
462+-- | Merge two 'Dynamic's into a new one using the provided function. The new
463+-- 'Dynamic' changes its value each time one of the original 'Dynamic's changes
464+-- its value.
465+{-# DEPRECATED combineDyn "Use 'return (zipDynWith f a b)' instead of 'combineDyn f a b'; consider eliminating monadic style" #-}
466+combineDyn :: forall t m a b c. (Reflex t, Monad m) => (a -> b -> c) -> Dynamic t a -> Dynamic t b -> m (Dynamic t c)
467+combineDyn f a b = return $ zipDynWith f a b
468+
469+-- | A psuedo applicative version of ap for 'Dynamic'. Example useage:
470+--
471+-- > do
472+-- > person <- Person `mapDyn` dynFirstName
473+-- > `apDyn` dynListName
474+-- > `apDyn` dynAge
475+-- > `apDyn` dynAddress
476+{-# DEPRECATED apDyn "Use 'ffor m (<*> a)' instead of 'apDyn m a'; consider eliminating monadic style, since Dynamics are now Applicative and can be used with applicative style directly" #-}
477+apDyn :: forall t m a b. (Reflex t, Monad m)
478+ => m (Dynamic t (a -> b))
479+ -> Dynamic t a
480+ -> m (Dynamic t b)
481+apDyn m a = ffor m (<*> a)
482+
483+--TODO: The pattern of using hold (sample b0) can be reused in various places as a safe way of building certain kinds of Dynamics; see if we can factor this out
484+-- | This function no longer needs to be monadic, so it has been replaced by
485+-- 'demuxed', which is pure.
486+{-# DEPRECATED getDemuxed "Use 'return . demuxed d' instead of 'getDemuxed d'; consider eliminating monadic style" #-}
487+getDemuxed :: (Reflex t, Monad m, Eq k) => Demux t k -> k -> m (Dynamic t Bool)
488+getDemuxed d = return . demuxed d
489+
490+-- | This function no longer needs to be monadic, so it has been replaced by
491+-- 'distributeFHListOverDynPure', which is pure.
492+{-# DEPRECATED distributeFHListOverDyn "Use 'return . distributeFHListOverDynPure' instead; consider eliminating monadic style" #-}
493+distributeFHListOverDyn :: forall t m l. (Reflex t, Monad m, RebuildSortedHList l) => FHList (Dynamic t) l -> m (Dynamic t (HList l))
494+distributeFHListOverDyn = return . distributeFHListOverDynPure
495+
496+-- | This function no longer needs to be monadic, so it has been replaced by
497+-- 'collectDynPure', which is pure.
498+{-# DEPRECATED collectDyn "Use 'return . collectDynPure' instead; consider eliminating monadic style" #-}
499+collectDyn :: ( RebuildSortedHList (HListElems b)
500+ , IsHList a, IsHList b
501+ , AllAreFunctors (Dynamic t) (HListElems b)
502+ , Reflex t, Monad m
503+ , HListElems a ~ FunctorList (Dynamic t) (HListElems b)
504+ ) => a -> m (Dynamic t b)
505+collectDyn = return . collectDynPure
506+
507+-- | This function has been renamed to 'tagPromptlyDyn' to clarify its
508+-- semantics.
509+{-# DEPRECATED tagDyn "Use 'tagPromptlyDyn' instead" #-}
510+tagDyn :: Reflex t => Dynamic t a -> Event t b -> Event t a
511+tagDyn = tagPromptlyDyn
512+
513+-- | This function has been renamed to 'attachPromptlyDyn' to clarify its
514+-- semantics.
515+{-# DEPRECATED attachDyn "Use 'attachPromptlyDyn' instead" #-}
516+attachDyn :: Reflex t => Dynamic t a -> Event t b -> Event t (a, b)
517+attachDyn = attachPromptlyDyn
518+
519+-- | This function has been renamed to 'attachPromptlyDynWith' to clarify its
520+-- semantics.
521+{-# DEPRECATED attachDynWith "Use 'attachPromptlyDynWith' instead" #-}
522+attachDynWith :: Reflex t => (a -> b -> c) -> Dynamic t a -> Event t b -> Event t c
523+attachDynWith = attachPromptlyDynWith
524+
525+-- | This function has been renamed to 'attachPromptlyDynWithMaybe' to clarify
526+-- its semantics.
527+{-# DEPRECATED attachDynWithMaybe "Use 'attachPromptlyDynWithMaybe' instead" #-}
528+attachDynWithMaybe :: Reflex t => (a -> b -> Maybe c) -> Dynamic t a -> Event t b -> Event t c
529+attachDynWithMaybe = attachPromptlyDynWithMaybe
530+
531+-- | Combine an inner and outer 'Dynamic' such that the resulting 'Dynamic''s
532+-- current value will always be equal to the current value's current value, and
533+-- will change whenever either the inner or the outer (or both) values change.
534+{-# DEPRECATED joinDyn "Use 'join' instead" #-}
535+joinDyn :: Reflex t => Dynamic t (Dynamic t a) -> Dynamic t a
536+joinDyn = join
537+
538+-- | 'nubDyn''s behavior is not quite analogous to 'nub''s behavior, so it has
539+-- been renamed to 'uniqDyn'
540+{-# DEPRECATED nubDyn "Use 'uniqDyn' instead" #-}
541+nubDyn :: (Reflex t, Eq a) => Dynamic t a -> Dynamic t a
542+nubDyn = uniqDyn
src/Reflex/Dynamic/Nub.hs+44−6
@@ -1,6 +1,8 @@
11{-# LANGUAGE FlexibleInstances #-}
22{-# LANGUAGE MagicHash #-}
33{-# LANGUAGE MultiParamTypeClasses #-}
4+-- | This module provides a variation of 'Dynamic' values that uses cheap
5+-- pointer equality checks to reduce the amount of signal propagation needed.
46module Reflex.Dynamic.Nub
57 ( NubDynamic
68 , nubDynamic
@@ -13,25 +15,61 @@ import GHC.Exts
1315import Reflex.Class
1416import Reflex.Dynamic
1517
18+-- | A 'Dynamic' whose 'updated' 'Event' will never fire with the same value as
19+-- the 'current' 'Behavior''s contents. In order to maintain this constraint,
20+-- the value inside a 'NubDynamic' is always evaluated to
21+-- <https://wiki.haskell.org/Weak_head_normal_form weak head normal form>.
22+--
23+-- Internally, 'NubDynamic' uses pointer equality as a heuristic to avoid
24+-- unnecessary update propagation; this is much more efficient than performing
25+-- full comparisons. However, when the 'NubDynamic' is converted back into a
26+-- regular 'Dynamic', a full comparison is performed.
1627newtype NubDynamic t a = NubDynamic { unNubDynamic :: Dynamic t a }
1728
29+-- | Construct a 'NubDynamic' by eliminating redundant updates from a 'Dynamic'.
1830nubDynamic :: Reflex t => Dynamic t a -> NubDynamic t a
1931nubDynamic d = NubDynamic $ unsafeBuildDynamic (sample $ current d) $ flip push (updated d) $ \new -> do --TODO: It would be very nice if we had an uncached push here
2032 old <- sample $ current d --TODO: Is it better to sample ourselves here?
2133 return $ unsafeJustChanged old new
2234
35+-- | Retrieve a normal 'Dynamic' from a 'NubDynamic'. This will perform a final
36+-- check using the output type's 'Eq' instance to ensure deterministic behavior.
37+--
38+-- WARNING: If used with a type whose 'Eq' instance is not law-abiding -
39+-- specifically, if there are cases where @x /= x@, 'fromNubDynamic' may
40+-- eliminate more 'updated' occurrences than it should. For example, NaN values
41+-- of 'Double' and 'Float' are considered unequal to themselves by the 'Eq'
42+-- instance, but can be equal by pointer equality. This may cause 'NubDynamic'
43+-- to lose changes from NaN to NaN.
2344fromNubDynamic :: (Reflex t, Eq a) => NubDynamic t a -> Dynamic t a
24−fromNubDynamic (NubDynamic d) = nubDyn d
45+fromNubDynamic (NubDynamic d) = uniqDynBy superEq d
46+ where
47+ -- Only consider values different if they fail both pointer equality /and/
48+ -- 'Eq' equality. This is to make things a bit more deterministic in the
49+ -- case of unlawful 'Eq' instances. However, it is still possible to
50+ -- achieve nondeterminism by constructing elements that are identical in
51+ -- value, unequal according to 'Eq', and nondeterministically equal or
52+ -- nonequal by pointer quality. I suspect that it is impossible to make the
53+ -- behavior deterministic in this case.
54+ superEq a b = a `unsafePtrEq` b || a == b
2555
26−-- | Create a NubDynamic without nubbing it on creation
27−-- This will be slightly faster than nubDynamic when used with a Dynamic whose values are always (or nearly always) different from its previous values; if used with a Dynamic whose values do not change frequently, it may be much slower than nubDynamic
56+-- | Create a NubDynamic without nubbing it on creation. This will be slightly
57+-- faster than nubDynamic when used with a Dynamic whose values are always (or
58+-- nearly always) different from its previous values; if used with a Dynamic
59+-- whose values do not change frequently, it may be much slower than nubDynamic
2860alreadyNubbedDynamic :: Dynamic t a -> NubDynamic t a
2961alreadyNubbedDynamic = NubDynamic
3062
63+unsafePtrEq :: a -> a -> Bool
64+unsafePtrEq a b = case a `seq` b `seq` reallyUnsafePtrEquality# a b of
65+ 0# -> False
66+ _ -> True
67+
3168unsafeJustChanged :: a -> a -> Maybe a
32−unsafeJustChanged old new = case old `seq` new `seq` reallyUnsafePtrEquality# old new of
33− 0# -> Just new
34− _ -> Nothing
69+unsafeJustChanged old new =
70+ if old `unsafePtrEq` new
71+ then Nothing
72+ else Just new
3573
3674instance Reflex t => Accumulator t (NubDynamic t) where
3775 accumMaybeM f z e = do
src/Reflex/Dynamic/TH.hs+20−4
@@ -4,6 +4,7 @@
44{-# LANGUAGE ScopedTypeVariables #-}
55{-# LANGUAGE TemplateHaskell #-}
66{-# LANGUAGE TypeOperators #-}
7+-- | Template Haskell helper functions for building complex 'Dynamic' values.
78module Reflex.Dynamic.TH (qDyn, unqDyn, mkDyn) where
89
910import Reflex.Dynamic
@@ -18,7 +19,9 @@ import Language.Haskell.TH
1819import Language.Haskell.TH.Quote
1920import qualified Language.Haskell.TH.Syntax as TH
2021
21−-- | Quote a Dynamic expression. Within the quoted expression, you can use '$(unqDyn [| x |])' to refer to any expression 'x' of type 'Dynamic t a'; the unquoted result will be of type 'a'
22+-- | Quote a 'Dynamic' expression. Within the quoted expression, you can use
23+-- @$(unqDyn [| x |])@ to refer to any expression @x@ of type @Dynamic t a@; the
24+-- unquoted result will be of type @a@
2225qDyn :: Q Exp -> Q Exp
2326qDyn qe = do
2427 e <- qe
@@ -35,19 +38,32 @@ qDyn qe = do
3538 let exprs = reverse exprsReversed
3639 arg = foldr (\a b -> ConE 'FHCons `AppE` a `AppE` b) (ConE 'FHNil) $ map snd exprs
3740 param = foldr (\a b -> ConP 'HCons [VarP a, b]) (ConP 'HNil []) $ map fst exprs
38− [| mapDyn $(return $ LamE [param] e') =<< distributeFHListOverDyn $(return arg) |]
41+ [| return $ $(return $ LamE [param] e') $ distributeFHListOverDynPure $(return arg) |]
3942
43+-- | Antiquote a 'Dynamic' expression. This can /only/ be used inside of a
44+-- 'qDyn' quotation.
4045unqDyn :: Q Exp -> Q Exp
4146unqDyn e = [| unqMarker $e |]
4247
43−-- | This type represents an occurrence of unqDyn before it has been processed by qDyn. If you see it in a type error, it probably means that unqDyn has been used outside of a qDyn context.
48+-- | This type represents an occurrence of unqDyn before it has been processed
49+-- by qDyn. If you see it in a type error, it probably means that unqDyn has
50+-- been used outside of a qDyn context.
4451data UnqDyn
4552
46−-- unqMarker must not be exported; it is used only as a way of smuggling data from unqDyn to qDyn
53+-- unqMarker must not be exported; it is used only as a way of smuggling data
54+-- from unqDyn to qDyn
55+
4756--TODO: It would be much nicer if the TH AST was extensible to support this kind of thing without trickery
4857unqMarker :: a -> UnqDyn
4958unqMarker = error "An unqDyn expression was used outside of a qDyn expression"
5059
60+-- | Create a 'Dynamic' value using other 'Dynamic's as inputs. The result is
61+-- sometimes more concise and readable than the equivalent 'Applicative'-based
62+-- expression. For example:
63+--
64+-- > [mkDyn| $x + $v * $t + 1/2 * $a * $t ^ 2 |]
65+--
66+-- would have a very cumbersome 'Applicative' encoding.
5167mkDyn :: QuasiQuoter
5268mkDyn = QuasiQuoter
5369 { quoteExp = mkDynExp
src/Reflex/Host/Class.hs+82−37
@@ -9,7 +9,20 @@
99{-# LANGUAGE ScopedTypeVariables #-}
1010{-# LANGUAGE TypeFamilies #-}
1111{-# LANGUAGE UndecidableInstances #-}
12−module Reflex.Host.Class where
12+-- | This module provides the interface for hosting 'Reflex' engines. This
13+-- should only be necessary if you're writing a binding or some other library
14+-- that provides a core event loop.
15+module Reflex.Host.Class
16+ ( ReflexHost (..)
17+ , MonadSubscribeEvent (..)
18+ , MonadReadEvent (..)
19+ , MonadReflexCreateTrigger (..)
20+ , MonadReflexHost (..)
21+ , fireEvents
22+ , newEventWithTriggerRef
23+ , fireEventRef
24+ , fireEventRefAndRead
25+ ) where
1326
1427import Reflex.Class
1528
@@ -33,33 +46,47 @@ import Data.Monoid
3346-- Note: this import must come last to silence warnings from AMP
3447import Prelude hiding (foldl, mapM, mapM_, sequence, sequence_)
3548
36−-- | Framework implementation support class for the reflex implementation represented by @t@.
37−class (Reflex t, MonadReflexCreateTrigger t (HostFrame t), MonadSample t (HostFrame t), MonadHold t (HostFrame t), MonadFix (HostFrame t), MonadSubscribeEvent t (HostFrame t)) => ReflexHost t where
49+-- | Framework implementation support class for the reflex implementation
50+-- represented by @t@.
51+class ( Reflex t
52+ , MonadReflexCreateTrigger t (HostFrame t)
53+ , MonadSample t (HostFrame t)
54+ , MonadHold t (HostFrame t)
55+ , MonadFix (HostFrame t)
56+ , MonadSubscribeEvent t (HostFrame t)
57+ ) => ReflexHost t where
3858 type EventTrigger t :: * -> *
3959 type EventHandle t :: * -> *
4060 type HostFrame t :: * -> *
4161
42−-- | Monad in which Events can be 'subscribed'. This forces all underlying event sources to be initialized, so that the event will fire whenever it ought to. Events must be subscribed before they are read using readEvent
62+-- | Monad in which Events can be 'subscribed'. This forces all underlying
63+-- event sources to be initialized, so that the event will fire whenever it
64+-- ought to. Events must be subscribed before they are read using readEvent
4365class (Reflex t, Monad m) => MonadSubscribeEvent t m | m -> t where
4466 -- | Subscribe to an event and set it up if needed.
4567 --
46− -- This function will create a new 'EventHandle' from an 'Event'. This handle may then be used via
47− -- 'readEvent' in the read callback of 'fireEventsAndRead'.
68+ -- This function will create a new 'EventHandle' from an 'Event'. This handle
69+ -- may then be used via 'readEvent' in the read callback of
70+ -- 'fireEventsAndRead'.
4871 --
49− -- If the event wasn't subscribed to before (either manually or through a dependent event or behavior)
50− -- then this function will cause the event and all dependencies of this event to be set up.
51− -- For example, if the event was created by 'newEventWithTrigger', then it's callback will be executed.
72+ -- If the event wasn't subscribed to before (either manually or through a
73+ -- dependent event or behavior) then this function will cause the event and
74+ -- all dependencies of this event to be set up. For example, if the event was
75+ -- created by 'newEventWithTrigger', then it's callback will be executed.
5276 --
5377 -- It's safe to call this function multiple times.
5478 subscribeEvent :: Event t a -> m (EventHandle t a)
5579
5680-- | Monad that allows to read events' values.
5781class (ReflexHost t, Applicative m, Monad m) => MonadReadEvent t m | m -> t where
58− -- | Read the value of an 'Event' from an 'EventHandle' (created by calling 'subscribeEvent').
82+ -- | Read the value of an 'Event' from an 'EventHandle' (created by calling
83+ -- 'subscribeEvent').
5984 --
60− -- After event propagation is done, all events can be in two states: either they are firing with some value or they are not firing.
61− -- In the former case, this function returns @Just act@, where @act@ in an action to read the current value of
62− -- the event. In the latter case, the function returns @Nothing@.
85+ -- After event propagation is done, all events can be in two states: either
86+ -- they are firing with some value or they are not firing. In the former
87+ -- case, this function returns @Just act@, where @act@ in an action to read
88+ -- the current value of the event. In the latter case, the function returns
89+ -- @Nothing@.
6390 --
6491 -- This function is normally used in the calllback for 'fireEventsAndRead'.
6592 readEvent :: EventHandle t a -> m (Maybe (m a))
@@ -68,42 +95,54 @@ class (ReflexHost t, Applicative m, Monad m) => MonadReadEvent t m | m -> t wher
6895class (Applicative m, Monad m) => MonadReflexCreateTrigger t m | m -> t where
6996 -- | Creates a root 'Event' (one that is not based on any other event).
7097 --
71− -- When a subscriber first subscribes to an event (building another event
72− -- that depends on the subscription) the given callback function is run and
73− -- passed a trigger. The callback function can then set up the event source in IO.
74− -- After this is done, the callback function must return an accompanying teardown action.
98+ -- When a subscriber first subscribes to an event (building another event that
99+ -- depends on the subscription) the given callback function is run and passed
100+ -- a trigger. The callback function can then set up the event source in IO.
101+ -- After this is done, the callback function must return an accompanying
102+ -- teardown action.
75103 --
76− -- Any time between setup and teardown the trigger can be used to fire
77− -- the event, by passing it to 'fireEventsAndRead'.
104+ -- Any time between setup and teardown the trigger can be used to fire the
105+ -- event, by passing it to 'fireEventsAndRead'.
78106 --
79− -- Note: An event may be set up multiple times. So after the teardown action is executed, the event may still be set up again in the future.
107+ -- Note: An event may be set up multiple times. So after the teardown action
108+ -- is executed, the event may still be set up again in the future.
80109 newEventWithTrigger :: (EventTrigger t a -> IO (IO ())) -> m (Event t a)
81110 newFanEventWithTrigger :: GCompare k => (forall a. k a -> EventTrigger t a -> IO (IO ())) -> m (EventSelector t k)
82111
83−class (ReflexHost t, MonadReflexCreateTrigger t m, MonadSubscribeEvent t m, MonadReadEvent t (ReadPhase m), MonadSample t (ReadPhase m), MonadHold t (ReadPhase m)) => MonadReflexHost t m | m -> t where
112+-- | 'MonadReflexHost' designates monads that can run reflex frames.
113+class ( ReflexHost t
114+ , MonadReflexCreateTrigger t m
115+ , MonadSubscribeEvent t m
116+ , MonadReadEvent t (ReadPhase m)
117+ , MonadSample t (ReadPhase m)
118+ , MonadHold t (ReadPhase m)
119+ ) => MonadReflexHost t m | m -> t where
84120 type ReadPhase m :: * -> *
85121 -- | Propagate some events firings and read the values of events afterwards.
86122 --
87− -- This function will create a new frame to fire the given events. It will then update all
88− -- dependent events and behaviors. After that is done, the given callback is executed which
89− -- allows to read the final values of events and check whether they have fired in this frame or
90− -- not.
123+ -- This function will create a new frame to fire the given events. It will
124+ -- then update all dependent events and behaviors. After that is done, the
125+ -- given callback is executed which allows to read the final values of events
126+ -- and check whether they have fired in this frame or not.
91127 --
92128 -- All events that are given are fired at the same time.
93129 --
94− -- This function is typically used in the main loop of a reflex framework implementation.
95− -- The main loop waits for external events to happen (such as keyboard input or a mouse click)
96− -- and then fires the corresponding events using this function. The read callback can be used
97− -- to read output events and perform a corresponding response action to the external event.
130+ -- This function is typically used in the main loop of a reflex framework
131+ -- implementation. The main loop waits for external events to happen (such as
132+ -- keyboard input or a mouse click) and then fires the corresponding events
133+ -- using this function. The read callback can be used to read output events
134+ -- and perform a corresponding response action to the external event.
98135 fireEventsAndRead :: [DSum (EventTrigger t) Identity] -> ReadPhase m a -> m a
99136
100137 -- | Run a frame without any events firing.
101138 --
102− -- This function should be used when you want to use 'sample' and 'hold' when no events are currently firing.
103− -- Using this function in that case can improve performance, since the implementation can assume that no events
104− -- are firing when 'sample' or 'hold' are called.
139+ -- This function should be used when you want to use 'sample' and 'hold' when
140+ -- no events are currently firing. Using this function in that case can
141+ -- improve performance, since the implementation can assume that no events are
142+ -- firing when 'sample' or 'hold' are called.
105143 --
106− -- This function is commonly used to set up the basic event network when the application starts up.
144+ -- This function is commonly used to set up the basic event network when the
145+ -- application starts up.
107146 runHostFrame :: HostFrame t a -> m a
108147
109148-- | Like 'fireEventsAndRead', but without reading any events.
@@ -113,10 +152,13 @@ fireEvents dm = fireEventsAndRead dm $ return ()
113152
114153-- | Create a new event and store its trigger in an 'IORef' while it's active.
115154--
116−-- An event is only active between the set up (when it's first subscribed to) and the teardown phases (when noboby is subscribing the event anymore).
117−-- This function returns an Event and an 'IORef'. As long as the event is active, the 'IORef' will contain 'Just' the event trigger to
118−-- trigger this event. When the event is not active, the 'IORef' will contain 'Nothing'. This allows event sources to be more efficient,
119−-- since they don't need to produce events when nobody is listening.
155+-- An event is only active between the set up (when it's first subscribed to)
156+-- and the teardown phases (when noboby is subscribing the event anymore). This
157+-- function returns an Event and an 'IORef'. As long as the event is active, the
158+-- 'IORef' will contain 'Just' the event trigger to trigger this event. When the
159+-- event is not active, the 'IORef' will contain 'Nothing'. This allows event
160+-- sources to be more efficient, since they don't need to produce events when
161+-- nobody is listening.
120162newEventWithTriggerRef :: (MonadReflexCreateTrigger t m, MonadRef m, Ref m ~ Ref IO) => m (Event t a, Ref m (Maybe (EventTrigger t a)))
121163newEventWithTriggerRef = do
122164 rt <- newRef Nothing
@@ -126,6 +168,7 @@ newEventWithTriggerRef = do
126168 return (e, rt)
127169{-# INLINE newEventWithTriggerRef #-}
128170
171+-- | Fire the given trigger if it is not 'Nothing'.
129172fireEventRef :: (MonadReflexHost t m, MonadRef m, Ref m ~ Ref IO) => Ref m (Maybe (EventTrigger t a)) -> a -> m ()
130173fireEventRef mtRef input = do
131174 mt <- readRef mtRef
@@ -133,6 +176,8 @@ fireEventRef mtRef input = do
133176 Nothing -> return ()
134177 Just trigger -> fireEvents [trigger :=> Identity input]
135178
179+-- | Fire the given trigger if it is not 'Nothing', and read from the given
180+-- 'EventHandle'.
136181fireEventRefAndRead :: (MonadReflexHost t m, MonadRef m, Ref m ~ Ref IO) => Ref m (Maybe (EventTrigger t a)) -> a -> EventHandle t b -> m (Maybe b)
137182fireEventRefAndRead mtRef input e = do
138183 mt <- readRef mtRef
src/Reflex/Spider.hs+2
@@ -1,3 +1,5 @@
1+-- | This module exports all of the user-facing functionality of the 'Spider'
2+-- 'Reflex' engine
13module Reflex.Spider ( Spider
24 , SpiderEnv
35 , Global
src/Reflex/Spider/Internal.hs+56−57
@@ -18,7 +18,17 @@
1818{-# LANGUAGE TypeFamilies #-}
1919{-# LANGUAGE TypeOperators #-}
2020{-# LANGUAGE UndecidableInstances #-}
21−module Reflex.Spider.Internal (Spider, SpiderEnv (..), Global, SpiderHost, runSpiderHost, SpiderEventHandle (..), showSubscriberType, showEventType, ComputeM (..), MergeSubscribedParent (..), HasSpiderEnv (..), newSpiderEnv, SpiderPullM (..), SpiderPushM (..), ReadPhase (..)) where
21+-- | This module is the implementation of the 'Spider' 'Reflex' engine. It uses
22+-- a graph traversal algorithm to propagate 'Event's and 'Behavior's.
23+module Reflex.Spider.Internal
24+ ( Spider
25+ , SpiderEnv (..)
26+ , Global
27+ , SpiderHost
28+ , runSpiderHost
29+ , newSpiderEnv
30+ , askSpiderEnv
31+ ) where
2232
2333import Control.Applicative
2434import Control.Concurrent
@@ -116,6 +126,33 @@ instance HasNodeId (Pull a) where
116126showNodeId :: HasNodeId a => a -> String
117127showNodeId = ("#"<>) . show . getNodeId
118128
129+showSubscriberType :: Subscriber a -> String
130+showSubscriberType = \case
131+ SubscriberPush _ _ -> "SubscriberPush"
132+ SubscriberMerge _ _ -> "SubscriberMerge"
133+ SubscriberFan _ -> "SubscriberFan"
134+ SubscriberMergeChange _ -> "SubscriberMergeChange"
135+ SubscriberHold _ -> "SubscriberHold"
136+ SubscriberHoldIdentity _ -> "SubscriberHoldIdentity"
137+ SubscriberSwitch _ -> "SubscriberSwitch"
138+ SubscriberCoincidenceOuter _ -> "SubscriberCoincidenceOuter"
139+ SubscriberCoincidenceInner _ -> "SubscriberCoincidenceInner"
140+ SubscriberHandle -> "SubscriberHandle"
141+
142+showEventType :: Event a -> String
143+showEventType = \case
144+ EventRoot _ _ -> "EventRoot"
145+ EventNever -> "EventNever"
146+ EventPush _ -> "EventPush"
147+ EventMerge _ -> "EventMerge"
148+ EventFan _ _ -> "EventFan"
149+ EventSwitch _ -> "EventSwitch"
150+ EventCoincidence _ -> "EventCoincidence"
151+ EventHold _ -> "EventHold"
152+ EventDyn _ -> "EventDyn"
153+ EventHoldIdentity _ -> "EventHoldIdentity"
154+ EventDynIdentity _ -> "EventDynIdentity"
155+
119156#else
120157
121158debugPropagate = False
@@ -218,6 +255,7 @@ instance HasCurrentHeight (EventM x) where
218255 liftIO $ modifyIORef' delayedRef $ IntMap.insertWith (++) (unHeight height) [DelayedMerge subscribed]
219256
220257class HasSpiderEnv x m | m -> x where
258+ -- | Retrieve the current SpiderEnv
221259 askSpiderEnv :: m (SpiderEnv x)
222260
223261instance HasSpiderEnv x (EventM x) where
@@ -275,12 +313,6 @@ hold v0 e = do
275313 defer $ SomeHoldInit h
276314 return h
277315
278−{-
279−mkWeakIORefKey :: IORef a -> b -> IO () -> IO (Weak b)
280−mkWeakIORefKey r@(IORef (STRef r#)) b (IO f) = IO $ \s ->
281− case mkWeak# r# b f s of (# s1, w #) -> (# s1, Weak w #)
282−-}
283−
284316{-# INLINE getHoldEventSubscription #-}
285317getHoldEventSubscription :: forall m p a x. (Defer SomeAssignment m, Defer SomeHoldInit m, Defer SomeClear m, R.Patch p, HasCurrentHeight m, Defer SomeMergeInit m, Defer SomeResetCoincidence m, HasSpiderEnv x m) => Hold p a -> m (EventSubscription (p a))
286318getHoldEventSubscription h = do
@@ -289,20 +321,6 @@ getHoldEventSubscription h = do
289321 Right subd -> return subd
290322 Left e -> do
291323 subscriptionRef <- liftIO $ newIORef $ error "getHoldEventSubscription: subdRef uninitialized"
292− {-
293− spiderEnv <- askSpiderEnv
294− weakSubscriptionRef <- liftIO $ mkWeakIORef subscriptionRef $ return ()
295− wh <- liftIO $ mkWeakIORefKey (holdValue h) h $ do
296− msr <- deRefWeak weakSubscriptionRef
297− forM_ msr $ \sr -> do
298− subscription <- readIORef sr
299− atomicModifyIORef (_spiderEnv_toUnsubscribe spiderEnv) $ \x -> (SomeEventSubscription subscription : x, ())
300−-- return ()
301− {-
302− -- What can we do? 1) Detect GCs at the SpiderEnv level, then go over the tree
303− 2) Have a finalizer on every SubscriberListNode that only runs if its parent is still alive, and does a normal unsubscribe if so; this would seem to double the number of weakRefs we use, but perhaps not the weakDepth, so maybe it's OK; we will probably need to move the regular call to unsubscribe into the finalizer and then use finalize instead of that call; we can have a strong reference to the SubscriberListNode, which means we just need a single weak reference to each EventSubscribed, which we can reuse in all of the finalizers that need to (potentially) unsubscribe from it
304−-}
305− -}
306324 subscription@(EventSubscription _ subd) <- subscribe e =<< liftIO (newSubscriberHold h)
307325 liftIO $ writeIORef subscriptionRef $! subscription
308326 occ <- liftIO $ readEventSubscribed subd
@@ -405,7 +423,7 @@ data PushSubscribed a b
405423#endif
406424 }
407425
408−newtype ComputeM a = ComputeM { unComputeM :: ReaderT (IORef [SomeHoldInit]) IO a } deriving (Functor, Applicative, Monad, MonadIO, MonadFix)
426+newtype ComputeM a = ComputeM (ReaderT (IORef [SomeHoldInit]) IO a) deriving (Functor, Applicative, Monad, MonadIO, MonadFix)
409427
410428instance Defer SomeHoldInit ComputeM where
411429 getDeferralQueue = ComputeM ask
@@ -420,10 +438,10 @@ data Push a b
420438 , pushSubscribed :: !(IORef (Maybe (PushSubscribed a b))) --TODO: Can we replace this with an unsafePerformIO thunk?
421439 }
422440
423−data MergeSubscribedParent k a
424− = MergeSubscribedParent { mergeSubscribedParentSubscription :: !(EventSubscription a)
425− , mergeSubscribedParentSubscriber :: !(Subscriber a)
426− }
441+data MergeSubscribedParent k a = MergeSubscribedParent !(EventSubscription a) !(Subscriber a)
442+
443+mergeSubscribedParentSubscription :: MergeSubscribedParent k a -> EventSubscription a
444+mergeSubscribedParentSubscription (MergeSubscribedParent es _) = es
427445
428446data HeightBag = HeightBag
429447 { _heightBag_size :: {-# UNPACK #-} !Int
@@ -561,19 +579,6 @@ data Subscriber a
561579 | SubscriberCoincidenceInner (CoincidenceSubscribed a)
562580 | SubscriberHandle
563581
564−showSubscriberType :: Subscriber a -> String
565−showSubscriberType = \case
566− SubscriberPush _ _ -> "SubscriberPush"
567− SubscriberMerge _ _ -> "SubscriberMerge"
568− SubscriberFan _ -> "SubscriberFan"
569− SubscriberMergeChange _ -> "SubscriberMergeChange"
570− SubscriberHold _ -> "SubscriberHold"
571− SubscriberHoldIdentity _ -> "SubscriberHoldIdentity"
572− SubscriberSwitch _ -> "SubscriberSwitch"
573− SubscriberCoincidenceOuter _ -> "SubscriberCoincidenceOuter"
574− SubscriberCoincidenceInner _ -> "SubscriberCoincidenceInner"
575− SubscriberHandle -> "SubscriberHandle"
576−
577582data Event a
578583 = forall k. GCompare k => EventRoot !(k a) !(Root k)
579584 | EventNever
@@ -587,20 +592,6 @@ data Event a
587592 | forall b. (a ~ Identity b) => EventHoldIdentity !(Hold Identity b)
588593 | forall b. (a ~ Identity b) => EventDynIdentity !(Dyn Identity b)
589594
590−showEventType :: Event a -> String
591−showEventType = \case
592− EventRoot _ _ -> "EventRoot"
593− EventNever -> "EventNever"
594− EventPush _ -> "EventPush"
595− EventMerge _ -> "EventMerge"
596− EventFan _ _ -> "EventFan"
597− EventSwitch _ -> "EventSwitch"
598− EventCoincidence _ -> "EventCoincidence"
599− EventHold _ -> "EventHold"
600− EventDyn _ -> "EventDyn"
601− EventHoldIdentity _ -> "EventHoldIdentity"
602− EventDynIdentity _ -> "EventDynIdentity"
603−
604595data EventSubscription a = EventSubscription
605596 { _eventSubscription_ticket :: !(Maybe (SubscriberListTicket a))
606597 , _eventSubscription_subscribed :: !(EventSubscribed a)
@@ -1999,15 +1990,18 @@ invalidate toReconnectRef wis = do
19991990-- Reflex integration
20001991--------------------------------------------------------------------------------
20011992
1993+-- | The default, global Spider environment
20021994type Spider = SpiderEnv Global
20031995
1996+-- | Designates the default, global Spider timeline
20041997data Global
20051998
20061999{-# NOINLINE globalSpiderEnv #-}
20072000globalSpiderEnv :: SpiderEnv Global
20082001globalSpiderEnv = unsafePerformIO unsafeNewSpiderEnv
20092002
2010−type role SpiderEnv nominal
2003+-- | Stores all global data relevant to a particular Spider timeline; only one
2004+-- value should exist for each type @x@
20112005data SpiderEnv x = SpiderEnv
20122006 { _spiderEnv_toUnsubscribe :: !(IORef [SomeEventSubscription])
20132007 , _spiderEnv_lock :: !(MVar ())
@@ -2016,6 +2010,7 @@ data SpiderEnv x = SpiderEnv
20162010 , _spiderEnv_depth :: !(IORef Int)
20172011#endif
20182012 }
2013+type role SpiderEnv nominal
20192014
20202015instance GEq SpiderEnv where
20212016 a `geq` b = if _spiderEnv_toUnsubscribe a == _spiderEnv_toUnsubscribe b
@@ -2039,12 +2034,13 @@ unsafeNewSpiderEnv = do
20392034#endif
20402035 }
20412036
2037+-- | Create a new SpiderEnv
20422038newSpiderEnv :: IO (Some SpiderEnv)
20432039newSpiderEnv = Some.This <$> unsafeNewSpiderEnv
20442040
2045−newtype SpiderPullM x a = SpiderPullM { runSpiderPullM :: BehaviorM a } deriving (Functor, Applicative, Monad, MonadIO, MonadFix)
2041+newtype SpiderPullM x a = SpiderPullM (BehaviorM a) deriving (Functor, Applicative, Monad, MonadIO, MonadFix)
20462042
2047−newtype SpiderPushM x a = SpiderPushM { runSpiderPushM :: ComputeM a } deriving (Functor, Applicative, Monad, MonadIO, MonadFix)
2043+newtype SpiderPushM x a = SpiderPushM (ComputeM a) deriving (Functor, Applicative, Monad, MonadIO, MonadFix)
20482044
20492045instance R.Reflex (SpiderEnv x) where
20502046 newtype Behavior (SpiderEnv x) a = SpiderBehavior { unSpiderBehavior :: Behavior a }
@@ -2172,6 +2168,7 @@ instance MonadAtomicRef (EventM x) where
21722168 {-# INLINABLE atomicModifyRef #-}
21732169 atomicModifyRef r f = liftIO $ atomicModifyRef r f
21742170
2171+-- | The monad for actions that manipulate a Spider timeline identified by @x@
21752172newtype SpiderHost x a = SpiderHost { unSpiderHost :: ReaderT (SpiderEnv x) IO a } deriving (Functor, Applicative, MonadFix, MonadIO, MonadException, MonadAsyncException)
21762173
21772174instance Monad (SpiderHost x) where
@@ -2184,6 +2181,8 @@ instance Monad (SpiderHost x) where
21842181 {-# INLINABLE fail #-}
21852182 fail s = SpiderHost $ fail s
21862183
2184+-- | Run an action affecting the global Spider timeline; this will be guarded by
2185+-- a mutex for that timeline
21872186runSpiderHost :: SpiderHost Global a -> IO a
21882187runSpiderHost (SpiderHost a) = runReaderT a globalSpiderEnv
21892188
@@ -2251,7 +2250,7 @@ subscribeEventSpiderHost e = do
22512250 subscription <- runFrame $ subscribe (unSpiderEvent e) SubscriberHandle --TODO: Unsubscribe eventually (manually and/or with weak ref)
22522251 return $ SpiderEventHandle subscription
22532252
2254−newtype ReadPhase x a = ReadPhase { runReadPhase :: ResultM x a } deriving (Functor, Applicative, Monad, MonadFix)
2253+newtype ReadPhase x a = ReadPhase (ResultM x a) deriving (Functor, Applicative, Monad, MonadFix)
22552254
22562255instance R.MonadSample (SpiderEnv x) (ReadPhase x) where
22572256 {-# INLINABLE sample #-}
test/Reflex/Bench/Focused.hs+12−12
@@ -47,15 +47,15 @@ mergeTree n es | length es <= n = sum' es
4747 subTrees = map sum' merges
4848 sum' = fmap sum . mconcat . fmap (fmap (:[]) )
4949
50−mergeListDyn :: (Reflex t, MonadHold t m) => [Dynamic t a] -> m (Dynamic t [a])
51−mergeListDyn dyns = traverse (mapDyn pure) dyns >>= mconcatDyn
50+mergeListDyn :: Reflex t => [Dynamic t a] -> Dynamic t [a]
51+mergeListDyn = mconcat . fmap (fmap pure)
5252
53−mergeTreeDyn :: (Num a, Reflex t, MonadHold t m, MonadFix m) => Int -> [Dynamic t a] -> m (Dynamic t a)
53+mergeTreeDyn :: (Num a, Reflex t) => Int -> [Dynamic t a] -> Dynamic t a
5454mergeTreeDyn n dyns | length dyns <= n = sumDyns dyns
55− | otherwise = traverse sumDyns merges >>= mergeTreeDyn n
55+ | otherwise = mergeTreeDyn n $ fmap sumDyns merges
5656 where
57− merges = chunksOf n dyns
58− sumDyns ds = mergeListDyn ds >>= mapDyn sum
57+ merges = chunksOf n dyns
58+ sumDyns = fmap sum . mergeListDyn
5959
6060
6161
@@ -125,13 +125,13 @@ fmapChain :: (Functor f) => Word -> f Word -> f Word
125125fmapChain = iter (fmap (+1))
126126
127127mapDynChain :: (Reflex t, MonadHold t m) => Word -> Dynamic t Word -> m (Dynamic t Word)
128−mapDynChain = iterM (mapDyn (+1))
128+mapDynChain = iterM (return . fmap (+1))
129129
130130joinDynChain :: (Reflex t, MonadHold t m) => Word -> Dynamic t Word -> m (Dynamic t Word)
131−joinDynChain = iterM (\d -> joinDyn <$> mapDyn (const d) d)
131+joinDynChain = iterM (\d -> return $ join $ fmap (const d) d)
132132
133133combineDynChain :: (Reflex t, MonadHold t m) => Word -> Dynamic t Word -> m (Dynamic t Word)
134−combineDynChain = iterM (\d -> combineDyn (+) d d)
134+combineDynChain = iterM (\d -> return $ zipDynWith (+) d d)
135135
136136pingPong :: (Reflex t, MonadHold t m, MonadFix m) => Event t a -> Event t a -> m (Event t a)
137137pingPong e1 e2 = do
@@ -309,9 +309,9 @@ dynamics n =
309309 [ testE "mapDynChain" $ fmap updated $ mapDynChain n =<< d
310310 , testE "joinDynChain" $ fmap updated $ joinDynChain n =<< d
311311 , testE "combineDynChain" $ fmap updated $ combineDynChain n =<< d
312− , testE "dense mergeTree" $ fmap updated $ mergeTreeDyn 8 =<< dense
313− , testE "sparse mergeTree" $ fmap updated $ mergeTreeDyn 8 =<< sparse
314− , testE "mergeDyn" $ fmap updated $ mapDyn sum =<< mergeListDyn =<< sparse
312+ , testE "dense mergeTree" $ fmap (updated . mergeTreeDyn 8) dense
313+ , testE "sparse mergeTree" $ fmap (updated . mergeTreeDyn 8) sparse
314+ , testE "mergeDyn" $ fmap (updated . fmap sum . mergeListDyn) sparse
315315 ] where
316316
317317 d :: TestPlan t m => m (Dynamic t Word)
test/Reflex/Pure.hs+19−13
@@ -1,8 +1,7 @@
1−{- | This module provides a pure implementation of Reflex, which is intended
2−to serve as a reference for the semantics of the Reflex class. All implementations
3−of Reflex should produce the same results as this implementation, although performance
4−and laziness/strictness may differ.
5−-}
1+-- | This module provides a pure implementation of Reflex, which is intended to
2+-- serve as a reference for the semantics of the Reflex class. All
3+-- implementations of Reflex should produce the same results as this
4+-- implementation, although performance and laziness/strictness may differ.
65
76{-# LANGUAGE EmptyDataDecls #-}
87{-# LANGUAGE FlexibleInstances #-}
@@ -23,7 +22,6 @@ import Control.Monad
2322import Data.Dependent.Map (DMap, GCompare)
2423import qualified Data.Dependent.Map as DMap
2524import Data.Functor.Identity
26−import Data.Functor.Misc
2725import Data.Maybe
2826import Data.MemoTrie
2927import Data.Monoid
@@ -32,8 +30,8 @@ import Reflex.Class
3230data Pure t
3331
3432-- | The Enum instance of t must be dense: for all x :: t, there must not exist
35−-- any y :: t such that pred x < y < x. The HasTrie instance will be used exclusively
36−-- to memoize functions of t, not for any of its other capabilities.
33+-- any y :: t such that pred x < y < x. The HasTrie instance will be used
34+-- exclusively to memoize functions of t, not for any of its other capabilities.
3735instance (Enum t, HasTrie t, Ord t) => Reflex (Pure t) where
3836
3937 newtype Behavior (Pure t) a = Behavior { unBehavior :: t -> a }
@@ -56,10 +54,14 @@ instance (Enum t, HasTrie t, Ord t) => Reflex (Pure t) where
5654 pull :: PullM (Pure t) a -> Behavior (Pure t) a
5755 pull = Behavior . memo
5856
59− -- [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
57+ -- [UNUSED_CONSTRAINT]: The following type signature for merge will produce a
58+ -- warning because the GCompare instance is not used; however, removing the
59+ -- GCompare instance produces a different warning, due to that constraint
60+ -- being present in the original class definition
61+
6062 --merge :: GCompare k => DMap k (Event (Pure t)) -> Event (Pure t) (DMap k Identity)
6163 merge events = Event $ memo $ \t ->
62− let currentOccurrences = unwrapDMapMaybe (($ t) . unEvent) events
64+ let currentOccurrences = DMap.mapMaybeWithKey (\_ (Event a) -> Identity <$> a t) events
6365 in if DMap.null currentOccurrences
6466 then Nothing
6567 else Just currentOccurrences
@@ -83,7 +85,9 @@ instance (Enum t, HasTrie t, Ord t) => Reflex (Pure t) where
8385 unsafeBuildDynamic readV0 v' = Dynamic $ \t -> (readV0 t, unEvent v' t)
8486
8587 -- See UNUSED_CONSTRAINT, above.
86− --unsafeBuildIncremental :: Patch p => PullM (Pure t) a -> Event (Pure t) (p a) -> Incremental (Pure t) p a
88+
89+ --unsafeBuildIncremental :: Patch p => PullM (Pure t) a -> Event (Pure t) (p
90+ --a) -> Incremental (Pure t) p a
8791 unsafeBuildIncremental readV0 p = Incremental $ \t -> (readV0 t, unEvent p t)
8892
8993 mergeIncremental (Incremental f) = Event $ \t ->
@@ -133,7 +137,8 @@ instance (Enum t, HasTrie t, Ord t) => MonadHold (Pure t) ((->) t) where
133137 hold initialValue e initialTime = Behavior f
134138 where f = memo $ \sampleTime ->
135139 -- Really, the sampleTime should never be prior to the initialTime,
136− -- because that would mean the Behavior is being sampled before being created.
140+ -- because that would mean the Behavior is being sampled before
141+ -- being created.
137142 if sampleTime <= initialTime
138143 then initialValue
139144 else let lastTime = pred sampleTime
@@ -148,7 +153,8 @@ instance (Enum t, HasTrie t, Ord t) => MonadHold (Pure t) ((->) t) where
148153 holdIncremental initialValue e initialTime = Incremental $ \t -> (f t, unEvent e t)
149154 where f = memo $ \sampleTime ->
150155 -- Really, the sampleTime should never be prior to the initialTime,
151− -- because that would mean the Behavior is being sampled before being created.
156+ -- because that would mean the Behavior is being sampled before
157+ -- being created.
152158 if sampleTime <= initialTime
153159 then initialValue
154160 else let lastTime = pred sampleTime
test/Reflex/Test/Micro.hs+15−15
@@ -67,9 +67,9 @@ testCases =
6767
6868 , testE "leftmost" $ liftA2 leftmost2 events1 events2
6969
70− , testE "appendEvents-1" $ liftA2 appendEvents events1 events2
70+ , testE "appendEvents-1" $ liftA2 mappend events1 events2
7171
72− , testE "appendEvents-2" $ liftA2 appendEvents events3 events2
72+ , testE "appendEvents-2" $ liftA2 mappend events3 events2
7373
7474 , testE "merge-1" $ do
7575 e <- events1
@@ -81,14 +81,14 @@ testCases =
8181 let ee = flip pushAlways e $ const $ return m
8282 return $ coincidence ee
8383
84− , testE "onceE-1" $ do
84+ , testE "headE-1" $ do
8585 e <- events1
86− onceE $ leftmost [e, e]
86+ headE $ leftmost [e, e]
8787
88− , testE "onceE-2" $ do
88+ , testE "headE-2" $ do
8989 e <- events1
9090 b <- hold never (e <$ e)
91− onceE $ switch b
91+ headE $ switch b
9292
9393 , testE "switch-1" $ do
9494 e <- events1
@@ -143,7 +143,7 @@ testCases =
143143 , testE "switchPromptly-5" $ do
144144 e <- events1
145145 switchPromptly never $ flip push e $
146− const (Just <$> onceE e)
146+ const (Just <$> headE e)
147147
148148 , testE "switchPromptly-6" $ do
149149 e <- events1
@@ -168,7 +168,7 @@ testCases =
168168 , testE "coincidence-4" $ do
169169 e <- events1
170170 return $ coincidence $ flip pushAlways e $
171− const (onceE e)
171+ const (headE e)
172172
173173 , testE "coincidence-5" $ do
174174 e <- events1
@@ -188,16 +188,16 @@ testCases =
188188
189189 , testB "holdWhileFiring" $ do
190190 e <- events1
191− eo <- onceE e
191+ eo <- headE e
192192 bb <- hold (constant "x") $ pushAlways (const $ hold "a" eo) eo
193193 return $ pull $ sample =<< sample bb
194194
195195 , testE "joinDyn" $ do
196196 e <- events1
197197 bb <- hold "b" e
198− bd <- hold never . fmap (const e) =<< onceE e
198+ bd <- hold never . fmap (const e) =<< headE e
199199
200− eOuter <- pushAlways sample . fmap (const bb) <$> onceE e
200+ eOuter <- pushAlways sample . fmap (const bb) <$> headE e
201201 let eInner = switch bd
202202 return $ leftmost [eOuter, eInner]
203203
@@ -207,13 +207,13 @@ testCases =
207207
208208 , testB "mapDyn" $ do
209209 d <- foldDyn (++) "0" =<< events1
210− current <$> mapDyn (map toUpper) d
210+ return $ current $ fmap (map toUpper) d
211211
212212 , testB "combineDyn" $ do
213213 d1 <- foldDyn (++) "0" =<< events1
214− d2 <- mapDyn (map toUpper) =<< foldDyn (++) "0" =<< events2
214+ d2 <- fmap (fmap (map toUpper)) $ foldDyn (++) "0" =<< events2
215215
216− current <$> combineDyn (<>) d1 d2
216+ return $ current $ zipDynWith (<>) d1 d2
217217 , testE "fan-1" $ do
218218 e <- fmap toMap <$> events1
219219 let es = select (fanMap e) . Const2 <$> values
@@ -248,7 +248,7 @@ testCases =
248248 events1 = plan [(1, "a"), (2, "b"), (5, "c"), (7, "d"), (8, "e")]
249249 events2 = plan [(1, "e"), (3, "d"), (4, "c"), (6, "b"), (7, "a")]
250250
251− events3 = liftA2 appendEvents events1 events2
251+ events3 = liftA2 mappend events1 events2
252252
253253 values = "abcde"
254254 toMap str = Map.fromList $ map (\c -> (c, c)) str