Skip to content

Commit 9afdfce

Browse files
Merge pull request #320 from treeowl/canhazmerge
Generalize merge(s) to allow non Identity functors in DMap.
2 parents bccbe42 + e5d5946 commit 9afdfce

File tree

7 files changed

+161
-76
lines changed

7 files changed

+161
-76
lines changed

ChangeLog.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
11
# Revision history for reflex
22

3+
## Unreleased
4+
5+
* Generalize merging functions:
6+
`merge` to `mergeG`,
7+
`mergeIncremental` to `mergeIncrementalG`,
8+
`distributeDMapOverDynPure` to `distributeDMapOverDynPureG`,
9+
`mergeIncrementalWithMove` to `mergeIncrementalWithMoveG`.
10+
311
## 0.6.2.0
412

13+
* Fix `holdDyn` so that it is lazy in its event argument
14+
These produce `DMap`s whose values needn't be `Identity`.
515
* Stop using the now-deprecated `*Tag` classes (e.g., `ShowTag`).
616
* Fix `holdDyn` so that it is lazy in its event argument.
717

reflex.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ library
4949
data-default >= 0.5 && < 0.8,
5050
dependent-map >= 0.3 && < 0.4,
5151
exception-transformers == 0.4.*,
52+
profunctors,
5253
lens >= 4.7 && < 5,
5354
monad-control >= 1.0.1 && < 1.1,
5455
monoidal-containers == 0.4.*,

src/Reflex/Class.hs

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
{-# LANGUAGE TypeFamilies #-}
1818
{-# LANGUAGE TypeOperators #-}
1919
{-# LANGUAGE UndecidableInstances #-}
20+
{-# LANGUAGE Trustworthy #-}
2021
#ifdef USE_REFLEX_OPTIMIZER
2122
{-# OPTIONS_GHC -fplugin=Reflex.Optimizer #-}
2223
#endif
@@ -46,6 +47,9 @@ module Reflex.Class
4647
, pushAlways
4748
-- ** Combining 'Event's
4849
, leftmost
50+
, merge
51+
, mergeIncremental
52+
, mergeIncrementalWithMove
4953
, mergeMap
5054
, mergeIntMap
5155
, mergeMapIncremental
@@ -84,6 +88,7 @@ module Reflex.Class
8488
, gate
8589
-- ** Combining 'Dynamic's
8690
, distributeDMapOverDynPure
91+
, distributeDMapOverDynPureG
8792
, distributeListOverDyn
8893
, distributeListOverDynWith
8994
, zipDyn
@@ -255,7 +260,9 @@ class ( MonadHold t (PushM t)
255260
-- | Merge a collection of events; the resulting 'Event' will only occur if at
256261
-- least one input event is occurring, and will contain all of the input keys
257262
-- that are occurring simultaneously
258-
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
263+
mergeG :: GCompare k => (forall a. q a -> Event t (v a))
264+
-> DMap k q -> Event t (DMap k v)
265+
--TODO: Generalize to get rid of DMap use --TODO: Provide a type-level guarantee that the result is not empty
259266
-- | Efficiently fan-out an event to many destinations. You should save the
260267
-- result in a @let@-binding, and then repeatedly 'select' on the result to
261268
-- create child events
@@ -277,9 +284,14 @@ class ( MonadHold t (PushM t)
277284
-- that value.
278285
unsafeBuildIncremental :: Patch p => PullM t (PatchTarget p) -> Event t p -> Incremental t p
279286
-- | Create a merge whose parents can change over time
280-
mergeIncremental :: GCompare k => Incremental t (PatchDMap k (Event t)) -> Event t (DMap k Identity)
287+
mergeIncrementalG :: GCompare k
288+
=> (forall a. q a -> Event t (v a))
289+
-> Incremental t (PatchDMap k q)
290+
-> Event t (DMap k v)
281291
-- | Experimental: Create a merge whose parents can change over time; changing the key of an Event is more efficient than with mergeIncremental
282-
mergeIncrementalWithMove :: GCompare k => Incremental t (PatchDMapWithMove k (Event t)) -> Event t (DMap k Identity)
292+
mergeIncrementalWithMoveG :: GCompare k
293+
=> (forall a. q a -> Event t (v a))
294+
-> Incremental t (PatchDMapWithMove k q) -> Event t (DMap k v)
283295
-- | Extract the 'Behavior' component of an 'Incremental'
284296
currentIncremental :: Patch p => Incremental t p -> Behavior t (PatchTarget p)
285297
-- | Extract the 'Event' component of an 'Incremental'
@@ -1079,12 +1091,21 @@ instance (Reflex t, Monoid a) => Monoid (Dynamic t a) where
10791091
-- 'Dynamic' 'DMap'. Its implementation is more efficient than doing the same
10801092
-- through the use of multiple uses of 'zipDynWith' or 'Applicative' operators.
10811093
distributeDMapOverDynPure :: forall t k. (Reflex t, GCompare k) => DMap k (Dynamic t) -> Dynamic t (DMap k Identity)
1082-
distributeDMapOverDynPure dm = case DMap.toList dm of
1094+
distributeDMapOverDynPure = distributeDMapOverDynPureG coerceDynamic
1095+
1096+
-- | This function converts a 'DMap' whose elements are 'Dynamic's into a
1097+
-- 'Dynamic' 'DMap'. Its implementation is more efficient than doing the same
1098+
-- through the use of multiple uses of 'zipDynWith' or 'Applicative' operators.
1099+
distributeDMapOverDynPureG
1100+
:: forall t k q v. (Reflex t, GCompare k)
1101+
=> (forall a. q a -> Dynamic t (v a))
1102+
-> DMap k q -> Dynamic t (DMap k v)
1103+
distributeDMapOverDynPureG nt dm = case DMap.toList dm of
10831104
[] -> constDyn DMap.empty
1084-
[k :=> v] -> fmap (DMap.singleton k . Identity) v
1105+
[k :=> v] -> DMap.singleton k <$> nt v
10851106
_ ->
1086-
let getInitial = DMap.traverseWithKey (\_ -> fmap Identity . sample . current) dm
1087-
edmPre = merge $ DMap.map updated dm
1107+
let getInitial = DMap.traverseWithKey (\_ -> sample . current . nt) dm
1108+
edmPre = mergeG getCompose $ DMap.map (Compose . updated . nt) dm
10881109
result = unsafeBuildDynamic getInitial $ flip pushAlways edmPre $ \news -> do
10891110
olds <- sample $ current result
10901111
return $ DMap.unionWithKey (\_ _ new -> new) olds news
@@ -1557,6 +1578,23 @@ fmapCheap f = pushCheap $ return . Just . f
15571578
tagCheap :: Reflex t => Behavior t b -> Event t a -> Event t b
15581579
tagCheap b = pushAlwaysCheap $ \_ -> sample b
15591580

1581+
-- | Merge a collection of events; the resulting 'Event' will only occur if at
1582+
-- least one input event is occurring, and will contain all of the input keys
1583+
-- that are occurring simultaneously
1584+
merge :: (Reflex t, GCompare k) => DMap k (Event t) -> Event t (DMap k Identity)
1585+
merge = mergeG coerceEvent
1586+
{-# INLINE merge #-}
1587+
1588+
-- | Create a merge whose parents can change over time
1589+
mergeIncremental :: (Reflex t, GCompare k)
1590+
=> Incremental t (PatchDMap k (Event t)) -> Event t (DMap k Identity)
1591+
mergeIncremental = mergeIncrementalG coerceEvent
1592+
1593+
-- | Experimental: Create a merge whose parents can change over time; changing the key of an Event is more efficient than with mergeIncremental
1594+
mergeIncrementalWithMove :: (Reflex t, GCompare k)
1595+
=> Incremental t (PatchDMapWithMove k (Event t)) -> Event t (DMap k Identity)
1596+
mergeIncrementalWithMove = mergeIncrementalWithMoveG coerceEvent
1597+
15601598
-- | A "cheap" version of 'mergeWithCheap'. See the performance note on 'pushCheap'.
15611599
{-# INLINE mergeWithCheap #-}
15621600
mergeWithCheap :: Reflex t => (a -> a -> a) -> [Event t a] -> Event t a

src/Reflex/Profiled.hs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
{-# LANGUAGE StandaloneDeriving #-}
99
{-# LANGUAGE TypeFamilies #-}
1010
{-# LANGUAGE UndecidableInstances #-}
11+
{-# LANGUAGE PolyKinds #-}
12+
{-# LANGUAGE RankNTypes #-}
1113
-- |
1214
-- Module:
1315
-- Reflex.Profiled
@@ -16,7 +18,6 @@
1618
-- profiling/cost-center information.
1719
module Reflex.Profiled where
1820

19-
import Control.Lens hiding (children)
2021
import Control.Monad
2122
import Control.Monad.Exception
2223
import Control.Monad.Fix
@@ -33,6 +34,7 @@ import Data.Map (Map)
3334
import qualified Data.Map.Strict as Map
3435
import Data.Monoid ((<>))
3536
import Data.Ord
37+
import Data.Profunctor.Unsafe ((#.))
3638
import qualified Data.Semigroup as S
3739
import Data.Type.Coercion
3840
import Foreign.Ptr
@@ -133,17 +135,19 @@ instance Reflex t => Reflex (ProfiledTimeline t) where
133135
push f (Event_Profiled e) = coerce $ push (coerce f) $ profileEvent e -- Profile before rather than after; this way fanout won't count against us
134136
pushCheap f (Event_Profiled e) = coerce $ pushCheap (coerce f) $ profileEvent e
135137
pull = Behavior_Profiled . pull . coerce
136-
merge :: forall k. GCompare k => DMap k (Event (ProfiledTimeline t)) -> Event (ProfiledTimeline t) (DMap k Identity)
137-
merge = Event_Profiled . merge . (unsafeCoerce :: DMap k (Event (ProfiledTimeline t)) -> DMap k (Event t))
138+
mergeG :: forall (k :: z -> *) q v. GCompare k
139+
=> (forall a. q a -> Event (ProfiledTimeline t) (v a))
140+
-> DMap k q -> Event (ProfiledTimeline t) (DMap k v)
141+
mergeG nt = Event_Profiled #. mergeG (coerce nt)
138142
fan (Event_Profiled e) = EventSelector $ coerce $ select (fan $ profileEvent e)
139143
switch (Behavior_Profiled b) = coerce $ profileEvent $ switch (coerceBehavior b)
140144
coincidence (Event_Profiled e) = coerce $ profileEvent $ coincidence (coerceEvent e)
141145
current (Dynamic_Profiled d) = coerce $ current d
142146
updated (Dynamic_Profiled d) = coerce $ profileEvent $ updated d
143147
unsafeBuildDynamic (ProfiledM a0) (Event_Profiled a') = coerce $ unsafeBuildDynamic a0 a'
144148
unsafeBuildIncremental (ProfiledM a0) (Event_Profiled a') = coerce $ unsafeBuildIncremental a0 a'
145-
mergeIncremental = Event_Profiled . mergeIncremental . (unsafeCoerce :: Incremental (ProfiledTimeline t) (PatchDMap k (Event (ProfiledTimeline t))) -> Incremental t (PatchDMap k (Event t)))
146-
mergeIncrementalWithMove = Event_Profiled . mergeIncrementalWithMove . (unsafeCoerce :: Incremental (ProfiledTimeline t) (PatchDMapWithMove k (Event (ProfiledTimeline t))) -> Incremental t (PatchDMapWithMove k (Event t)))
149+
mergeIncrementalG nt = (Event_Profiled . coerce) #. mergeIncrementalG nt
150+
mergeIncrementalWithMoveG nt = (Event_Profiled . coerce) #. mergeIncrementalWithMoveG nt
147151
currentIncremental (Incremental_Profiled i) = coerce $ currentIncremental i
148152
updatedIncremental (Incremental_Profiled i) = coerce $ profileEvent $ updatedIncremental i
149153
incrementalToDynamic (Incremental_Profiled i) = coerce $ incrementalToDynamic i

src/Reflex/Pure.hs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
{-# LANGUAGE MultiParamTypeClasses #-}
66
{-# LANGUAGE ScopedTypeVariables #-}
77
{-# LANGUAGE TypeFamilies #-}
8+
{-# LANGUAGE RankNTypes #-}
9+
{-# LANGUAGE PolyKinds #-}
810
#ifdef USE_REFLEX_OPTIMIZER
911
{-# OPTIONS_GHC -fplugin=Reflex.Optimizer #-}
1012
#endif
@@ -43,10 +45,11 @@ import Data.MemoTrie
4345
import Data.Monoid
4446
import Data.Type.Coercion
4547
import Reflex.Class
48+
import Data.Kind (Type)
4649

4750
-- | A completely pure-functional 'Reflex' timeline, identifying moments in time
4851
-- with the type @/t/@.
49-
data Pure t
52+
data Pure (t :: Type)
5053

5154
-- | The 'Enum' instance of @/t/@ must be dense: for all @/x :: t/@, there must not exist
5255
-- any @/y :: t/@ such that @/'pred' x < y < x/@. The 'HasTrie' instance will be used
@@ -79,11 +82,12 @@ instance (Enum t, HasTrie t, Ord t) => Reflex (Pure t) where
7982
-- [UNUSED_CONSTRAINT]: The following type signature for merge will produce a
8083
-- warning because the GCompare instance is not used; however, removing the
8184
-- GCompare instance produces a different warning, due to that constraint
82-
-- being present in the original class definition
85+
-- being present in the original class definition.
8386

84-
--merge :: GCompare k => DMap k (Event (Pure t)) -> Event (Pure t) (DMap k Identity)
85-
merge events = Event $ memo $ \t ->
86-
let currentOccurrences = DMap.mapMaybeWithKey (\_ (Event a) -> Identity <$> a t) events
87+
--mergeG :: GCompare k => (forall a. q a -> Event (Pure t) (v a))
88+
-- -> DMap k q -> Event (Pure t) (DMap k v)
89+
mergeG nt events = Event $ memo $ \t ->
90+
let currentOccurrences = DMap.mapMaybeWithKey (\_ q -> case nt q of Event a -> a t) events
8791
in if DMap.null currentOccurrences
8892
then Nothing
8993
else Just currentOccurrences
@@ -112,8 +116,8 @@ instance (Enum t, HasTrie t, Ord t) => Reflex (Pure t) where
112116
--a) -> Incremental (Pure t) p a
113117
unsafeBuildIncremental readV0 p = Incremental $ \t -> (readV0 t, unEvent p t)
114118

115-
mergeIncremental = mergeIncrementalImpl
116-
mergeIncrementalWithMove = mergeIncrementalImpl
119+
mergeIncrementalG = mergeIncrementalImpl
120+
mergeIncrementalWithMoveG = mergeIncrementalImpl
117121

118122
currentIncremental i = Behavior $ \t -> fst $ unIncremental i t
119123

@@ -133,9 +137,11 @@ instance (Enum t, HasTrie t, Ord t) => Reflex (Pure t) where
133137

134138
mergeIntIncremental = mergeIntIncrementalImpl
135139

136-
mergeIncrementalImpl :: (PatchTarget p ~ DMap k (Event (Pure t)), GCompare k) => Incremental (Pure t) p -> Event (Pure t) (DMap k Identity)
137-
mergeIncrementalImpl i = Event $ \t ->
138-
let results = DMap.mapMaybeWithKey (\_ (Event e) -> Identity <$> e t) $ fst $ unIncremental i t
140+
mergeIncrementalImpl :: (PatchTarget p ~ DMap k q, GCompare k)
141+
=> (forall a. q a -> Event (Pure t) (v a))
142+
-> Incremental (Pure t) p -> Event (Pure t) (DMap k v)
143+
mergeIncrementalImpl nt i = Event $ \t ->
144+
let results = DMap.mapMaybeWithKey (\_ q -> case nt q of Event e -> e t) $ fst $ unIncremental i t
139145
in if DMap.null results
140146
then Nothing
141147
else Just results

0 commit comments

Comments
 (0)