on develop help sign in
Merge branch 'develop' into runhost
commit15a75455b821a56473c5080945847030404c7987 authorElliot Cameron <3noch@users.noreply.github.com> dateThu 2 Apr 2020, 17:59 (7 years ago) parentb2009f3fc79984
37 files changed+1019−1462
.travis.yml+4 ChangeLog.md+24 README.md+8−2 cabal.haskell-ci+6 cabal.project.freeze+1 dep/reflex-platform/github.json+3−3 reflex.cabal+92−17 src/Data/AppendMap.hs+1−1 src/Data/FastMutableIntMap.hs+7−3 src/Data/Functor/Misc.hs−235 src/Data/Map/Misc.hs+1−1 src/Reflex/Adjustable/Class.hs+1−1 src/Reflex/BehaviorWriter/Base.hs+3−3 src/Reflex/BehaviorWriter/Class.hs+11−5 src/Reflex/Class.hs+4−4 src/Reflex/DynamicWriter/Base.hs+1−1 src/Reflex/FastWeak.hs+10 src/Reflex/Patch.hs−46 src/Reflex/Patch/Class.hs−34 src/Reflex/Patch/DMap.hs−82 src/Reflex/Patch/DMapWithMove.hs−363 src/Reflex/Patch/IntMap.hs−59 src/Reflex/Patch/Map.hs−55 src/Reflex/Patch/MapWithMove.hs−270 src/Reflex/PerformEvent/Class.hs+6 src/Reflex/Profiled.hs+55−3 src/Reflex/Query/Base.hs+1−1 src/Reflex/Query/Class.hs+44−4 src/Reflex/Spider/Internal.hs+419−256 src/Reflex/TriggerEvent/Class.hs+6 src/Reflex/Widget/Basic.hs+1−1 test/Adjustable.hs+62 test/DebugCycles.hs+171 test/GC.hs+1−2 test/QueryT.hs+1−1 test/Reflex/Test/CrossImpl.hs+12−7 test/RequesterT.hs+63−2
.travis.yml+4
@@ -168,6 +168,10 @@ script:
168168 - if $GHCJS || ! $GHCJS && [ $HCNUMVER -lt 80800 ] ; then ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --constraint='these <1' all | color_cabal_output ; fi
169169 # Constraint set old-witherable
170170 - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --constraint='witherable <0.3.2' all | color_cabal_output
171+ # Constraint set debug-propagation
172+ - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --constraint='reflex +debug-propagation' all | color_cabal_output
173+ # Constraint set debug-cycles
174+ - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --constraint='reflex +debug-cycles' all | color_cabal_output
171175
172176# REGENDATA ["--config=cabal.haskell-ci","cabal.project"]
173177# EOF
ChangeLog.md+24
@@ -2,10 +2,34 @@
22
33## Unreleased
44
5+* Add lifting instances for most classes to `Reflex.Profiled.Profiled`.
6+* Class `MonadQuery t q m` now has a `Monad m` superclass constraint.
7+* Rename class `MonadBehaviorWriter` -> `BehaviorWriter` for
8+ consistency with `EventWriter`/`DynamicWriter`.
9+* Introduce deprecated alias `MonadBehaviorWriter = BehaviorWriter`.
10+
11+## 0.6.4.1
12+
13+* Fix a bug in the Reflex Profiled transformer where
14+ `Reflex.Class.mergeIncrementalG` and
15+ `Reflex.Class.mergeIncrementalWithMoveG` implementations referenced
16+ itself instead of the inner transformed timeline, causing an
17+ infinite loop.
18+
19+## 0.6.4
20+
521* Support GHC 8.8
622
723* Add `Reflex.Query.Base.mapQueryT`. See that module for documentation
824
25+* The `Reflex.Patch.*` modules were moved to the `patch` library.
26+ They are `Data.Patch.*` there, but reexported under their old names for backwards compatability here.
27+
28+* Additional instances for `Query` classes for basic types.
29+
30+* Add cabal flags `debug-propagation` and `debug-event-cycles` to build in debugging
31+ code for performance and for cyclic dependencies between events
32+
933## 0.6.3
1034
1135* `Data.WeakBag.traverse` and `Data.FastWeakBag.traverse` have been deprecated.
README.md+8−2
@@ -21,5 +21,11 @@ A summary of Reflex functions is available in the [quick reference](Quickref.md)
2121
2222### Hacking
2323
24−Use the `./scripts/hack-on reflex` script in [Reflex Platform](https://github.com/reflex-frp/reflex-platform) to checkout the source code of `reflex` locally in `reflex-platform/reflex` directory.
25−Then do modifications to the source in place, and use the `./try-reflex` or `./scripts/work-on` scripts to create the shell to test your changes.
24+From the root of a [Reflex
25+Platform](https://github.com/reflex-frp/reflex-platform) checkout, run
26+`./scripts/hack-on haskell-overlays/reflex-packages/dep/reflex`. This
27+will check out the reflex source code into the
28+`haskell-overlays/reflex-packages/dep/reflex` directory. You can then
29+point that checkout at your fork, make changes, etc. Use the
30+`./try-reflex` or `./scripts/work-on` scripts to start a shell in
31+which you can test your changes.
cabal.haskell-ci+6
@@ -15,3 +15,9 @@ constraint-set old-these
1515
1616constraint-set old-witherable
1717 constraints: witherable <0.3.2
18+
19+constraint-set debug-propagation
20+ constraints: reflex +debug-propagation
21+
22+constraint-set debug-cycles
23+ constraints: reflex +debug-cycles
cabal.project.freeze+1
@@ -1 +1,2 @@
11constraints: any.text < 1.2.4.0
2+ , hlint < 2.2.6 || > 2.2.6
dep/reflex-platform/github.json+3−3
@@ -1,7 +1,7 @@
11{
22 "owner": "reflex-frp",
33 "repo": "reflex-platform",
4− "branch": "master",
5− "rev": "510b990d0b11f0626afbec5fe8575b5b2395391b",
6− "sha256": "09cmahsbxr0963wq171c7j139iyzz49hramr4v9nsf684wcwkngv"
4+ "branch": "develop",
5+ "rev": "f628398d076243a0851b27e625b37f65dff9b89b",
6+ "sha256": "0sl0hf1glgyb1vmf2mhw4r9ipmcqk1y19d3wsic7dix2jwywzrh9"
77}
reflex.cabal+92−17
@@ -1,5 +1,5 @@
11Name: reflex
2−Version: 0.6.3
2+Version: 0.6.4.1
33Synopsis: Higher-order Functional Reactive Programming
44Description: Reflex is a high-performance, deterministic, higher-order Functional Reactive Programming system
55License: BSD3
@@ -9,7 +9,7 @@ Maintainer: ryan.trinkle@gmail.com
99Stability: Experimental
1010Category: FRP
1111Build-type: Simple
12−Cabal-version: >=1.9.2
12+Cabal-version: 1.22
1313homepage: https://reflex-frp.org
1414bug-reports: https://github.com/reflex-frp/reflex/issues
1515extra-source-files:
@@ -41,12 +41,23 @@ flag fast-weak
4141 default: False
4242 manual: True
4343
44+flag debug-propagation
45+ description: Enable debugging of spider internals
46+ default: False
47+ manual: True
48+
49+flag debug-cycles
50+ description: Enable debugging of event cycles
51+ default: False
52+ manual: True
53+
4454flag split-these
4555 description: Use split these/semialign packages
4656 manual: False
4757 default: True
4858
4959library
60+ default-language: Haskell2010
5061 hs-source-dirs: src
5162 build-depends:
5263 MemoTrie == 0.6.*,
@@ -61,6 +72,7 @@ library
6172 lens >= 4.7 && < 5,
6273 monad-control >= 1.0.1 && < 1.1,
6374 mtl >= 2.1 && < 2.3,
75+ patch >= 0.0.1 && < 0.1,
6476 prim-uniq >= 0.1.0.1 && < 0.2,
6577 primitive >= 0.5 && < 0.8,
6678 profunctors >= 5.3 && < 5.6,
@@ -88,7 +100,6 @@ library
88100 Data.AppendMap,
89101 Data.FastMutableIntMap,
90102 Data.FastWeakBag,
91− Data.Functor.Misc,
92103 Data.Map.Misc,
93104 Data.WeakBag,
94105 Reflex,
@@ -111,13 +122,6 @@ library
111122 Reflex.Main
112123 Reflex.Network,
113124 Reflex.NotReady.Class,
114− Reflex.Patch,
115− Reflex.Patch.Class,
116− Reflex.Patch.DMap,
117− Reflex.Patch.DMapWithMove,
118− Reflex.Patch.IntMap,
119− Reflex.Patch.Map,
120− Reflex.Patch.MapWithMove,
121125 Reflex.PerformEvent.Base,
122126 Reflex.PerformEvent.Class,
123127 Reflex.PostBuild.Base,
@@ -136,6 +140,16 @@ library
136140 Reflex.Widget.Basic,
137141 Reflex.Workflow
138142
143+ reexported-modules:
144+ patch:Data.Functor.Misc,
145+ patch:Data.Patch as Reflex.Patch,
146+ patch:Data.Patch.Class as Reflex.Patch.Class,
147+ patch:Data.Patch.DMap as Reflex.Patch.DMap,
148+ patch:Data.Patch.DMapWithMove as Reflex.Patch.DMapWithMove,
149+ patch:Data.Patch.IntMap as Reflex.Patch.IntMap,
150+ patch:Data.Patch.Map as Reflex.Patch.Map,
151+ patch:Data.Patch.MapWithMove as Reflex.Patch.MapWithMove
152+
139153 ghc-options: -Wall -fwarn-redundant-constraints -fwarn-tabs -funbox-strict-fields -O2 -fspecialise-aggressively
140154
141155 if flag(debug-trace-events)
@@ -147,6 +161,12 @@ library
147161 build-depends: ghc
148162 exposed-modules: Reflex.Optimizer
149163
164+ if flag(debug-propagation)
165+ cpp-options: -DDEBUG -DDEBUG_TRACE_PROPAGATION -DDEBUG_TRACE_INVALIDATION
166+
167+ if flag(debug-cycles)
168+ cpp-options: -DDEBUG_CYCLES
169+
150170 if flag(use-template-haskell)
151171 cpp-options: -DUSE_TEMPLATE_HASKELL
152172 build-depends:
@@ -168,6 +188,7 @@ library
168188 build-depends: ghcjs-base
169189
170190test-suite semantics
191+ default-language: Haskell2010
171192 type: exitcode-stdio-1.0
172193 main-is: semantics.hs
173194 hs-source-dirs: test
@@ -193,6 +214,7 @@ test-suite semantics
193214 Reflex.TestPlan
194215
195216test-suite CrossImpl
217+ default-language: Haskell2010
196218 type: exitcode-stdio-1.0
197219 main-is: Reflex/Test/CrossImpl.hs
198220 hs-source-dirs: test
@@ -214,6 +236,7 @@ test-suite CrossImpl
214236 Reflex.Plan.Pure
215237
216238test-suite hlint
239+ default-language: Haskell2010
217240 type: exitcode-stdio-1.0
218241 main-is: hlint.hs
219242 hs-source-dirs: test
@@ -226,6 +249,7 @@ test-suite hlint
226249 buildable: False
227250
228251test-suite EventWriterT
252+ default-language: Haskell2010
229253 type: exitcode-stdio-1.0
230254 main-is: EventWriterT.hs
231255 hs-source-dirs: test
@@ -245,28 +269,57 @@ test-suite EventWriterT
245269 build-depends: these-lens
246270
247271 other-modules:
248− Reflex.Test
249− Reflex.TestPlan
250− Reflex.Plan.Reflex
251− Reflex.Plan.Pure
252272 Test.Run
253273
254274
255−test-suite RequesterT
275+test-suite DebugCycles
276+ default-language: Haskell2010
256277 type: exitcode-stdio-1.0
257− main-is: RequesterT.hs
278+ main-is: DebugCycles.hs
258279 hs-source-dirs: test
280+ ghc-options: -threaded
259281 build-depends: base
260282 , containers
261283 , deepseq
262− , dependent-sum
263284 , dependent-map
285+ , dependent-sum
286+ , hspec
264287 , lens
265288 , mtl
266289 , these
267290 , transformers
268291 , reflex
269292 , ref-tf
293+ , witherable
294+ , proctest
295+
296+
297+ if flag(split-these)
298+ build-depends: these-lens, semialign
299+
300+ other-modules:
301+ Test.Run
302+
303+
304+test-suite RequesterT
305+ default-language: Haskell2010
306+ type: exitcode-stdio-1.0
307+ main-is: RequesterT.hs
308+ hs-source-dirs: test
309+ build-depends: base
310+ , constraints
311+ , constraints-extras
312+ , containers
313+ , deepseq
314+ , dependent-map
315+ , dependent-sum
316+ , lens
317+ , mtl
318+ , ref-tf
319+ , reflex
320+ , text
321+ , these
322+ , transformers
270323
271324 if flag(split-these)
272325 build-depends: these-lens
@@ -276,7 +329,23 @@ test-suite RequesterT
276329 Reflex.Plan.Pure
277330 Test.Run
278331
332+test-suite Adjustable
333+ default-language: Haskell2010
334+ type: exitcode-stdio-1.0
335+ main-is: Adjustable.hs
336+ hs-source-dirs: test
337+ build-depends: base
338+ , containers
339+ , dependent-sum
340+ , reflex
341+ , ref-tf
342+ , these
343+
344+ other-modules:
345+ Test.Run
346+
279347test-suite QueryT
348+ default-language: Haskell2010
280349 type: exitcode-stdio-1.0
281350 main-is: QueryT.hs
282351 hs-source-dirs: test
@@ -288,6 +357,7 @@ test-suite QueryT
288357 , lens
289358 , monoidal-containers
290359 , mtl
360+ , patch
291361 , ref-tf
292362 , reflex
293363 , these
@@ -303,6 +373,7 @@ test-suite QueryT
303373 Reflex.Plan.Pure
304374
305375test-suite GC-Semantics
376+ default-language: Haskell2010
306377 type: exitcode-stdio-1.0
307378 main-is: GC.hs
308379 hs-source-dirs: test
@@ -312,6 +383,7 @@ test-suite GC-Semantics
312383 , dependent-map
313384 , deepseq
314385 , mtl
386+ , patch
315387 , these
316388 , transformers
317389 , reflex
@@ -327,6 +399,7 @@ test-suite GC-Semantics
327399 Test.Run
328400
329401test-suite rootCleanup
402+ default-language: Haskell2010
330403 type: exitcode-stdio-1.0
331404 main-is: rootCleanup.hs
332405 hs-source-dirs: test
@@ -344,6 +417,7 @@ test-suite rootCleanup
344417 Test.Run
345418
346419benchmark spider-bench
420+ default-language: Haskell2010
347421 type: exitcode-stdio-1.0
348422 hs-source-dirs: bench test
349423 main-is: Main.hs
@@ -368,6 +442,7 @@ benchmark spider-bench
368442 Reflex.Bench.Focused
369443
370444benchmark saulzar-bench
445+ default-language: Haskell2010
371446 type: exitcode-stdio-1.0
372447 hs-source-dirs: bench test
373448 c-sources: bench-cbits/checkCapability.c
src/Data/AppendMap.hs+1−1
@@ -71,7 +71,7 @@ mapMaybeNoNull f as =
7171 then Nothing
7272 else Just bs
7373
74−-- TODO: Move instances to `Reflex.Patch`
74+-- TODO: Move instances to `Data.Patch`
7575-- | Displays a 'MonoidalMap' as a tree. See 'Data.Map.Lazy.showTree' for details.
7676showTree :: forall k a. (Show k, Show a) => MonoidalMap k a -> String
7777showTree = coerce (Map.showTree :: Map k a -> String)
src/Data/FastMutableIntMap.hs+7−3
@@ -21,6 +21,7 @@ module Data.FastMutableIntMap
2121 , patchIntMapNewElements
2222 , patchIntMapNewElementsMap
2323 , getDeletions
24+ , toList
2425 ) where
2526
2627--TODO: Pure JS version
@@ -34,8 +35,8 @@ import Data.Foldable (traverse_)
3435import Data.IntMap.Strict (IntMap)
3536import qualified Data.IntMap.Strict as IntMap
3637import Data.IORef
37−import Reflex.Patch.Class
38−import Reflex.Patch.IntMap
38+import Data.Patch.Class
39+import Data.Patch.IntMap
3940
4041-- | A 'FastMutableIntMap' holds a map of values of type @a@ and allows low-overhead modifications via IO.
4142-- Operations on 'FastMutableIntMap' run in IO.
@@ -84,10 +85,13 @@ getFrozenAndClear (FastMutableIntMap r) = do
8485 writeIORef r IntMap.empty
8586 return result
8687
87−-- | Updates the value of a 'FastMutableIntMap' with the given patch (see 'Reflex.Patch.IntMap'),
88+-- | Updates the value of a 'FastMutableIntMap' with the given patch (see 'Data.Patch.IntMap'),
8889-- and returns an 'IntMap' with the modified keys and values.
8990applyPatch :: FastMutableIntMap a -> PatchIntMap a -> IO (IntMap a)
9091applyPatch (FastMutableIntMap r) p@(PatchIntMap m) = do
9192 v <- readIORef r
9293 writeIORef r $! applyAlways p v
9394 return $ IntMap.intersection v m
95+
96+toList :: FastMutableIntMap a -> IO [(Int, a)]
97+toList (FastMutableIntMap r) = IntMap.toList <$> readIORef r
src/Data/Functor/Misc.hsdeleted−235
@@ -1,235 +0,0 @@
1−{-# LANGUAGE CPP #-}
2−{-# LANGUAGE DeriveDataTypeable #-}
3−{-# LANGUAGE DeriveFunctor #-}
4−{-# LANGUAGE FlexibleContexts #-}
5−{-# LANGUAGE FlexibleInstances #-}
6−{-# LANGUAGE GADTs #-}
7−{-# LANGUAGE LambdaCase #-}
8−{-# LANGUAGE MultiParamTypeClasses #-}
9−{-# LANGUAGE PatternSynonyms #-}
10−{-# LANGUAGE PolyKinds #-}
11−{-# LANGUAGE RankNTypes #-}
12−{-# LANGUAGE ScopedTypeVariables #-}
13−{-# LANGUAGE StandaloneDeriving #-}
14−#ifdef USE_REFLEX_OPTIMIZER
15−{-# OPTIONS_GHC -fplugin=Reflex.Optimizer #-}
16−#endif
17−-- | This module provides types and functions with no particular theme, but
18−-- which are relevant to the use of 'Functor'-based datastructures like
19−-- 'Data.Dependent.Map.DMap'.
20−module Data.Functor.Misc
21− ( -- * Const2
22− Const2 (..)
23− , unConst2
24− , dmapToMap
25− , dmapToIntMap
26− , dmapToMapWith
27− , mapToDMap
28− , weakenDMapWith
29− -- * WrapArg
30− , WrapArg (..)
31− -- * Convenience functions for DMap
32− , mapWithFunctorToDMap
33− , intMapWithFunctorToDMap
34− , mapKeyValuePairsMonotonic
35− , combineDMapsWithKey
36− , EitherTag (..)
37− , dmapToThese
38− , eitherToDSum
39− , dsumToEither
40− , ComposeMaybe (..)
41− ) where
42−
43−import Control.Applicative ((<$>))
44−import Control.Monad.Identity
45−import Data.Dependent.Map (DMap)
46−import qualified Data.Dependent.Map as DMap
47−import Data.Dependent.Sum
48−import Data.GADT.Compare
49−import Data.GADT.Show
50−import Data.IntMap (IntMap)
51−import qualified Data.IntMap as IntMap
52−import Data.Map (Map)
53−import qualified Data.Map as Map
54−import Data.Some (Some(Some))
55−import Data.These
56−import Data.Type.Equality ((:~:)(Refl))
57−import Data.Typeable hiding (Refl)
58−
59−--------------------------------------------------------------------------------
60−-- Const2
61−--------------------------------------------------------------------------------
62−
63−-- | 'Const2' stores a value of a given type 'k' and ensures that a particular
64−-- type 'v' is always given for the last type parameter
65−data Const2 :: * -> x -> x -> * where
66− Const2 :: k -> Const2 k v v
67− deriving (Typeable)
68−
69−-- | Extract the value from a Const2
70−unConst2 :: Const2 k v v' -> k
71−unConst2 (Const2 k) = k
72−
73−deriving instance Eq k => Eq (Const2 k v v')
74−deriving instance Ord k => Ord (Const2 k v v')
75−deriving instance Show k => Show (Const2 k v v')
76−deriving instance Read k => Read (Const2 k v v)
77−
78−instance Show k => GShow (Const2 k v) where
79− gshowsPrec n x@(Const2 _) = showsPrec n x
80−
81−instance Eq k => GEq (Const2 k v) where
82− geq (Const2 a) (Const2 b) =
83− if a == b
84− then Just Refl
85− else Nothing
86−
87−instance Ord k => GCompare (Const2 k v) where
88− gcompare (Const2 a) (Const2 b) = case compare a b of
89− LT -> GLT
90− EQ -> GEQ
91− GT -> GGT
92−
93−-- | Convert a 'DMap' to a regular 'Map'
94−dmapToMap :: DMap (Const2 k v) Identity -> Map k v
95−dmapToMap = Map.fromDistinctAscList . map (\(Const2 k :=> Identity v) -> (k, v)) . DMap.toAscList
96−
97−-- | Convert a 'DMap' to an 'IntMap'
98−dmapToIntMap :: DMap (Const2 IntMap.Key v) Identity -> IntMap v
99−dmapToIntMap = IntMap.fromDistinctAscList . map (\(Const2 k :=> Identity v) -> (k, v)) . DMap.toAscList
100−
101−-- | Convert a 'DMap' to a regular 'Map', applying the given function to remove
102−-- the wrapping 'Functor'
103−dmapToMapWith :: (f v -> v') -> DMap (Const2 k v) f -> Map k v'
104−dmapToMapWith f = Map.fromDistinctAscList . map (\(Const2 k :=> v) -> (k, f v)) . DMap.toAscList
105−
106−-- | Convert a regular 'Map' to a 'DMap'
107−mapToDMap :: Map k v -> DMap (Const2 k v) Identity
108−mapToDMap = DMap.fromDistinctAscList . map (\(k, v) -> Const2 k :=> Identity v) . Map.toAscList
109−
110−-- | Convert a regular 'Map', where the values are already wrapped in a functor,
111−-- to a 'DMap'
112−mapWithFunctorToDMap :: Map k (f v) -> DMap (Const2 k v) f
113−mapWithFunctorToDMap = DMap.fromDistinctAscList . map (\(k, v) -> Const2 k :=> v) . Map.toAscList
114−
115−-- | Convert a regular 'IntMap', where the values are already wrapped in a
116−-- functor, to a 'DMap'
117−intMapWithFunctorToDMap :: IntMap (f v) -> DMap (Const2 IntMap.Key v) f
118−intMapWithFunctorToDMap = DMap.fromDistinctAscList . map (\(k, v) -> Const2 k :=> v) . IntMap.toAscList
119−
120−-- | Convert a 'DMap' to a regular 'Map' by forgetting the types associated with
121−-- the keys, using a function to remove the wrapping 'Functor'
122−weakenDMapWith :: (forall a. v a -> v') -> DMap k v -> Map (Some k) v'
123−weakenDMapWith f = Map.fromDistinctAscList . map (\(k :=> v) -> (Some k, f v)) . DMap.toAscList
124−
125−--------------------------------------------------------------------------------
126−-- WrapArg
127−--------------------------------------------------------------------------------
128−
129−-- | 'WrapArg' can be used to tag a value in one functor with a type
130−-- representing another functor. This was primarily used with dependent-map <
131−-- 0.2, in which the value type was not wrapped in a separate functor.
132−data WrapArg :: (k -> *) -> (k -> *) -> * -> * where
133− WrapArg :: f a -> WrapArg g f (g a)
134−
135−deriving instance Eq (f a) => Eq (WrapArg g f (g' a))
136−deriving instance Ord (f a) => Ord (WrapArg g f (g' a))
137−deriving instance Show (f a) => Show (WrapArg g f (g' a))
138−deriving instance Read (f a) => Read (WrapArg g f (g a))
139−
140−instance GEq f => GEq (WrapArg g f) where
141− geq (WrapArg a) (WrapArg b) = (\Refl -> Refl) <$> geq a b
142−
143−instance GCompare f => GCompare (WrapArg g f) where
144− gcompare (WrapArg a) (WrapArg b) = case gcompare a b of
145− GLT -> GLT
146− GEQ -> GEQ
147− GGT -> GGT
148−
149−--------------------------------------------------------------------------------
150−-- Convenience functions for DMap
151−--------------------------------------------------------------------------------
152−
153−-- | Map over all key/value pairs in a 'DMap', potentially altering the key as
154−-- well as the value. The provided function MUST preserve the ordering of the
155−-- keys, or the resulting 'DMap' will be malformed.
156−mapKeyValuePairsMonotonic :: (DSum k v -> DSum k' v') -> DMap k v -> DMap k' v'
157−mapKeyValuePairsMonotonic f = DMap.fromDistinctAscList . map f . DMap.toAscList
158−
159−{-# INLINE combineDMapsWithKey #-}
160−-- | Union two 'DMap's of different types, yielding another type. Each key that
161−-- is present in either input map will be present in the output.
162−combineDMapsWithKey :: forall f g h i.
163− GCompare f
164− => (forall a. f a -> These (g a) (h a) -> i a)
165− -> DMap f g
166− -> DMap f h
167− -> DMap f i
168−combineDMapsWithKey f mg mh = DMap.fromList $ go (DMap.toList mg) (DMap.toList mh)
169− where go :: [DSum f g] -> [DSum f h] -> [DSum f i]
170− go [] hs = map (\(hk :=> hv) -> hk :=> f hk (That hv)) hs
171− go gs [] = map (\(gk :=> gv) -> gk :=> f gk (This gv)) gs
172− go gs@((gk :=> gv) : gs') hs@((hk :=> hv) : hs') = case gk `gcompare` hk of
173− GLT -> (gk :=> f gk (This gv)) : go gs' hs
174− GEQ -> (gk :=> f gk (These gv hv)) : go gs' hs'
175− GGT -> (hk :=> f hk (That hv)) : go gs hs'
176−
177−-- | Extract the values of a 'DMap' of 'EitherTag's.
178−dmapToThese :: DMap (EitherTag a b) Identity -> Maybe (These a b)
179−dmapToThese m = case (DMap.lookup LeftTag m, DMap.lookup RightTag m) of
180− (Nothing, Nothing) -> Nothing
181− (Just (Identity a), Nothing) -> Just $ This a
182− (Nothing, Just (Identity b)) -> Just $ That b
183− (Just (Identity a), Just (Identity b)) -> Just $ These a b
184−
185−-- | Tag type for 'Either' to use it as a 'DSum'.
186−data EitherTag l r a where
187− LeftTag :: EitherTag l r l
188− RightTag :: EitherTag l r r
189− deriving (Typeable)
190−
191−deriving instance Show (EitherTag l r a)
192−deriving instance Eq (EitherTag l r a)
193−deriving instance Ord (EitherTag l r a)
194−
195−instance GEq (EitherTag l r) where
196− geq a b = case (a, b) of
197− (LeftTag, LeftTag) -> Just Refl
198− (RightTag, RightTag) -> Just Refl
199− _ -> Nothing
200−
201−instance GCompare (EitherTag l r) where
202− gcompare a b = case (a, b) of
203− (LeftTag, LeftTag) -> GEQ
204− (LeftTag, RightTag) -> GLT
205− (RightTag, LeftTag) -> GGT
206− (RightTag, RightTag) -> GEQ
207−
208−instance GShow (EitherTag l r) where
209− gshowsPrec _ a = case a of
210− LeftTag -> showString "LeftTag"
211− RightTag -> showString "RightTag"
212−
213−-- | Convert 'Either' to a 'DSum'. Inverse of 'dsumToEither'.
214−eitherToDSum :: Either a b -> DSum (EitherTag a b) Identity
215−eitherToDSum = \case
216− Left a -> (LeftTag :=> Identity a)
217− Right b -> (RightTag :=> Identity b)
218−
219−-- | Convert 'DSum' to 'Either'. Inverse of 'eitherToDSum'.
220−dsumToEither :: DSum (EitherTag a b) Identity -> Either a b
221−dsumToEither = \case
222− (LeftTag :=> Identity a) -> Left a
223− (RightTag :=> Identity b) -> Right b
224−
225−--------------------------------------------------------------------------------
226−-- ComposeMaybe
227−--------------------------------------------------------------------------------
228−
229−-- | We can't use @Compose Maybe@ instead of 'ComposeMaybe', because that would
230−-- make the 'f' parameter have a nominal type role. We need f to be
231−-- representational so that we can use safe 'coerce'.
232−newtype ComposeMaybe f a =
233− ComposeMaybe { getComposeMaybe :: Maybe (f a) } deriving (Show, Eq, Ord)
234−
235−deriving instance Functor f => Functor (ComposeMaybe f)
src/Data/Map/Misc.hs+1−1
@@ -43,7 +43,7 @@ diffMap olds news = flip Map.mapMaybe (align olds news) $ \case
4343
4444-- |Given a @'Map' k (Maybe v)@ representing keys to insert/update (@Just@) or delete (@Nothing@), produce a new map from the given input @'Map' k v@.
4545--
46−-- See also 'Reflex.Patch.Map' and 'Reflex.Patch.MapWithMove'.
46+-- See also 'Data.Patch.Map' and 'Data.Patch.MapWithMove'.
4747applyMap :: Ord k => Map k (Maybe v) -> Map k v -> Map k v
4848applyMap patch old = insertions `Map.union` (old `Map.difference` deletions)
4949 where (deletions, insertions) = Map.mapEither maybeToEither patch
src/Reflex/Adjustable/Class.hs+1−1
@@ -39,7 +39,7 @@ import qualified Data.IntMap.Strict as IntMap
3939import Data.Map (Map)
4040
4141import Reflex.Class
42−import Reflex.Patch.DMapWithMove
42+import Data.Patch.DMapWithMove
4343
4444-- | A 'Monad' that supports adjustment over time. After an action has been
4545-- run, if the given events fire, it will adjust itself so that its net effect
src/Reflex/BehaviorWriter/Base.hs+3−3
@@ -1,6 +1,6 @@
11{-|
22Module: Reflex.BehaviorWriter.Base
3−Description: Implementation of MonadBehaviorWriter
3+Description: Implementation of BehaviorWriter
44-}
55{-# LANGUAGE CPP #-}
66{-# LANGUAGE FlexibleInstances #-}
@@ -46,7 +46,7 @@ import Reflex.Query.Class
4646import Reflex.Requester.Class
4747import Reflex.TriggerEvent.Class
4848
49−-- | A basic implementation of 'MonadBehaviorWriter'.
49+-- | A basic implementation of 'BehaviorWriter'.
5050newtype BehaviorWriterT t w m a = BehaviorWriterT { unBehaviorWriterT :: StateT [Behavior t w] m a }
5151 deriving (Functor, Applicative, Monad, MonadIO, MonadFix, MonadAsyncException, MonadException) -- The list is kept in reverse order
5252
@@ -89,7 +89,7 @@ instance MonadReflexCreateTrigger t m => MonadReflexCreateTrigger t (BehaviorWri
8989 newEventWithTrigger = lift . newEventWithTrigger
9090 newFanEventWithTrigger f = lift $ newFanEventWithTrigger f
9191
92−instance (Monad m, Monoid w, Reflex t) => MonadBehaviorWriter t w (BehaviorWriterT t w m) where
92+instance (Monad m, Monoid w, Reflex t) => BehaviorWriter t w (BehaviorWriterT t w m) where
9393 tellBehavior w = BehaviorWriterT $ modify (w :)
9494
9595instance MonadReader r m => MonadReader r (BehaviorWriterT t w m) where
src/Reflex/BehaviorWriter/Class.hs+11−5
@@ -1,8 +1,9 @@
11{-|
22Module: Reflex.BehaviorWriter.Class
3−Description: This module defines the 'MonadBehaviorWriter' class
3+Description: This module defines the 'BehaviorWriter' class
44-}
55{-# LANGUAGE CPP #-}
6+{-# LANGUAGE ConstraintKinds #-}
67{-# LANGUAGE FlexibleInstances #-}
78{-# LANGUAGE FunctionalDependencies #-}
89{-# LANGUAGE UndecidableInstances #-}
@@ -10,16 +11,21 @@ Description: This module defines the 'MonadBehaviorWriter' class
1011{-# OPTIONS_GHC -fplugin=Reflex.Optimizer #-}
1112#endif
1213module Reflex.BehaviorWriter.Class
13− ( MonadBehaviorWriter (..)
14+ ( MonadBehaviorWriter
15+ , BehaviorWriter(..)
1416 ) where
1517
1618import Control.Monad.Reader (ReaderT, lift)
1719import Reflex.Class (Behavior)
1820
19−-- | 'MonadBehaviorWriter' efficiently collects 'Behavior' values using 'tellBehavior'
21+{-# DEPRECATED MonadBehaviorWriter "Use 'BehaviorWriter' instead" #-}
22+-- | Type synonym for 'BehaviorWriter'
23+type MonadBehaviorWriter = BehaviorWriter
24+
25+-- | 'BehaviorWriter' efficiently collects 'Behavior' values using 'tellBehavior'
2026-- and combines them monoidally to provide a 'Behavior' result.
21−class (Monad m, Monoid w) => MonadBehaviorWriter t w m | m -> t w where
27+class (Monad m, Monoid w) => BehaviorWriter t w m | m -> t w where
2228 tellBehavior :: Behavior t w -> m ()
2329
24−instance MonadBehaviorWriter t w m => MonadBehaviorWriter t w (ReaderT r m) where
30+instance BehaviorWriter t w m => BehaviorWriter t w (ReaderT r m) where
2531 tellBehavior = lift . tellBehavior
src/Reflex/Class.hs+4−4
@@ -29,7 +29,7 @@
2929-- convenience functions for working with 'Event's, 'Behavior's, and other
3030-- signals.
3131module Reflex.Class
32− ( module Reflex.Patch
32+ ( module Data.Patch
3333 -- * Primitives
3434 , Reflex (..)
3535 , mergeInt
@@ -210,7 +210,7 @@ import Data.IntMap.Strict (IntMap)
210210import qualified Data.IntMap.Strict as IntMap
211211import Data.List.NonEmpty (NonEmpty (..))
212212import Data.Map (Map)
213−import Data.Semigroup (Semigroup, sconcat, stimes, (<>))
213+import Data.Semigroup (Semigroup (..))
214214import Data.Some (Some(Some))
215215import Data.String
216216import Data.These
@@ -220,8 +220,8 @@ import Data.Witherable (Filterable(..))
220220import qualified Data.Witherable as W
221221import Reflex.FunctorMaybe (FunctorMaybe)
222222import qualified Reflex.FunctorMaybe
223−import Reflex.Patch
224−import qualified Reflex.Patch.MapWithMove as PatchMapWithMove
223+import Data.Patch
224+import qualified Data.Patch.MapWithMove as PatchMapWithMove
225225
226226import Debug.Trace (trace)
227227
src/Reflex/DynamicWriter/Base.hs+1−1
@@ -44,7 +44,7 @@ import Reflex.Class
4444import Reflex.DynamicWriter.Class
4545import Reflex.EventWriter.Class (EventWriter, tellEvent)
4646import Reflex.Host.Class
47−import qualified Reflex.Patch.MapWithMove as MapWithMove
47+import qualified Data.Patch.MapWithMove as MapWithMove
4848import Reflex.PerformEvent.Class
4949import Reflex.PostBuild.Class
5050import Reflex.Query.Class
src/Reflex/FastWeak.hs+10
@@ -151,6 +151,16 @@ getFastWeakTicket w = do
151151
152152-- | Create a 'FastWeakTicket' directly from a value, creating a 'FastWeak' in the process
153153-- which can be obtained with 'getFastWeakTicketValue'.
154+--
155+-- This function is marked NOINLINE so it is opaque to GHC.
156+-- If we do not do this, then GHC will sometimes fuse the constructor away
157+-- so any weak references that are attached to the ticket will have their
158+-- finalizer run. Using the opaque constructor, GHC does not see the
159+-- constructor application, so it behaves like an IORef and cannot be fused away.
160+--
161+-- The result is also evaluated to WHNF, since forcing a thunk invalidates
162+-- the weak pointer to it in some cases.
163+{-# NOINLINE mkFastWeakTicket #-}
154164mkFastWeakTicket :: a -> IO (FastWeakTicket a)
155165-- I think it's fine if this is lazy - it'll retain the 'a', but so would the output; we just need to make sure it's forced before we start relying on the
156166-- associated FastWeak to actually be weak
src/Reflex/Patch.hsdeleted−46
@@ -1,46 +0,0 @@
1−{-# LANGUAGE TypeFamilies #-}
2−-- |
3−-- Module:
4−-- Reflex.Patch
5−-- Description:
6−-- This module defines the 'Patch' class, which is used by Reflex to manage
7−-- changes to 'Reflex.Class.Incremental' values.
8−module Reflex.Patch
9− ( module Reflex.Patch
10− , module X
11− ) where
12−
13−import Reflex.Patch.Class as X
14−import Reflex.Patch.DMap as X hiding (getDeletions)
15−import Reflex.Patch.DMapWithMove as X (PatchDMapWithMove, const2PatchDMapWithMoveWith, mapPatchDMapWithMove,
16− patchDMapWithMoveToPatchMapWithMoveWith,
17− traversePatchDMapWithMoveWithKey, unPatchDMapWithMove,
18− unsafePatchDMapWithMove, weakenPatchDMapWithMoveWith)
19−import Reflex.Patch.IntMap as X hiding (getDeletions)
20−import Reflex.Patch.Map as X
21−import Reflex.Patch.MapWithMove as X (PatchMapWithMove, patchMapWithMoveNewElements,
22− patchMapWithMoveNewElementsMap, unPatchMapWithMove,
23− unsafePatchMapWithMove)
24−import Data.Map.Monoidal (MonoidalMap)
25−import Data.Semigroup (Semigroup (..), (<>))
26−
27−-- | A 'Group' is a 'Monoid' where every element has an inverse.
28−class (Semigroup q, Monoid q) => Group q where
29− negateG :: q -> q
30− (~~) :: q -> q -> q
31− r ~~ s = r <> negateG s
32−
33−-- | An 'Additive' 'Semigroup' is one where (<>) is commutative
34−class Semigroup q => Additive q where
35−
36−-- | The elements of an 'Additive' 'Semigroup' can be considered as patches of their own type.
37−newtype AdditivePatch p = AdditivePatch { unAdditivePatch :: p }
38−
39−instance Additive p => Patch (AdditivePatch p) where
40− type PatchTarget (AdditivePatch p) = p
41− apply (AdditivePatch p) q = Just $ p <> q
42−
43−instance (Ord k, Group q) => Group (MonoidalMap k q) where
44− negateG = fmap negateG
45−
46−instance (Ord k, Additive q) => Additive (MonoidalMap k q)
src/Reflex/Patch/Class.hsdeleted−34
@@ -1,34 +0,0 @@
1−{-# LANGUAGE TypeFamilies #-}
2−-- | The interface for types which represent changes made to other types
3−module Reflex.Patch.Class where
4−
5−import Control.Monad.Identity
6−import Data.Maybe
7−import Data.Semigroup (Semigroup(..))
8−
9−-- | A 'Patch' type represents a kind of change made to a datastructure.
10−--
11−-- If an instance of 'Patch' is also an instance of 'Semigroup', it should obey
12−-- the law that @applyAlways (f <> g) == applyAlways f . applyAlways g@.
13−class Patch p where
14− type PatchTarget p :: *
15− -- | Apply the patch @p a@ to the value @a@. If no change is needed, return
16− -- 'Nothing'.
17− apply :: p -> PatchTarget p -> Maybe (PatchTarget p)
18−
19−-- | Apply a 'Patch'; if it does nothing, return the original value
20−applyAlways :: Patch p => p -> PatchTarget p -> PatchTarget p
21−applyAlways p t = fromMaybe t $ apply p t
22−
23−-- | 'Identity' can be used as a 'Patch' that always fully replaces the value
24−instance Patch (Identity a) where
25− type PatchTarget (Identity a) = a
26− apply (Identity a) _ = Just a
27−
28−-- | Like '(.)', but composes functions that return patches rather than
29−-- functions that return new values. The Semigroup instance for patches must
30−-- apply patches right-to-left, like '(.)'.
31−composePatchFunctions :: (Patch p, Semigroup p) => (PatchTarget p -> p) -> (PatchTarget p -> p) -> PatchTarget p -> p
32−composePatchFunctions g f a =
33− let fp = f a
34− in g (applyAlways fp a) <> fp
src/Reflex/Patch/DMap.hsdeleted−82
@@ -1,82 +0,0 @@
1−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
2−{-# LANGUAGE LambdaCase #-}
3−{-# LANGUAGE PolyKinds #-}
4−{-# LANGUAGE Rank2Types #-}
5−{-# LANGUAGE ScopedTypeVariables #-}
6−{-# LANGUAGE StandaloneDeriving #-}
7−{-# LANGUAGE TypeFamilies #-}
8−-- | 'Patch'es on 'DMap' that consist only of insertions (or overwrites) and deletions.
9−module Reflex.Patch.DMap where
10−
11−import Reflex.Patch.Class
12−import Reflex.Patch.IntMap
13−import Reflex.Patch.Map
14−
15−import Data.Dependent.Map (DMap, DSum (..), GCompare (..))
16−import qualified Data.Dependent.Map as DMap
17−import Data.Functor.Constant
18−import Data.Functor.Misc
19−import qualified Data.IntMap as IntMap
20−import qualified Data.Map as Map
21−import Data.Semigroup (Semigroup (..))
22−import Data.Some (Some)
23−
24−-- | A set of changes to a 'DMap'. Any element may be inserted/updated or deleted.
25−-- Insertions are represented as @'ComposeMaybe' (Just value)@,
26−-- while deletions are represented as @'ComposeMaybe' Nothing@.
27−newtype PatchDMap k v = PatchDMap { unPatchDMap :: DMap k (ComposeMaybe v) }
28−
29−deriving instance GCompare k => Semigroup (PatchDMap k v)
30−
31−deriving instance GCompare k => Monoid (PatchDMap k v)
32−
33−-- | Apply the insertions or deletions to a given 'DMap'.
34−instance GCompare k => Patch (PatchDMap k v) where
35− type PatchTarget (PatchDMap k v) = DMap k v
36− apply (PatchDMap diff) old = Just $! insertions `DMap.union` (old `DMap.difference` deletions) --TODO: return Nothing sometimes --Note: the strict application here is critical to ensuring that incremental merges don't hold onto all their prerequisite events forever; can we make this more robust?
37− where insertions = DMap.mapMaybeWithKey (const $ getComposeMaybe) diff
38− deletions = DMap.mapMaybeWithKey (const $ nothingToJust . getComposeMaybe) diff
39− nothingToJust = \case
40− Nothing -> Just $ Constant ()
41− Just _ -> Nothing
42−
43−-- | Map a function @v a -> v' a@ over any inserts/updates in the given
44−-- @'PatchDMap' k v@ to produce a @'PatchDMap' k v'@.
45−mapPatchDMap :: (forall a. v a -> v' a) -> PatchDMap k v -> PatchDMap k v'
46−mapPatchDMap f (PatchDMap p) = PatchDMap $ DMap.map (ComposeMaybe . fmap f . getComposeMaybe) p
47−
48−-- | Map an effectful function @v a -> f (v' a)@ over any inserts/updates in the given
49−-- @'PatchDMap' k v@ to produce a @'PatchDMap' k v'@.
50−traversePatchDMap :: Applicative f => (forall a. v a -> f (v' a)) -> PatchDMap k v -> f (PatchDMap k v')
51−traversePatchDMap f = traversePatchDMapWithKey $ const f
52−
53−-- | Map an effectful function @k a -> v a -> f (v' a)@ over any inserts/updates
54−-- in the given @'PatchDMap' k v@ to produce a @'PatchDMap' k v'@.
55−traversePatchDMapWithKey :: Applicative m => (forall a. k a -> v a -> m (v' a)) -> PatchDMap k v -> m (PatchDMap k v')
56−traversePatchDMapWithKey f (PatchDMap p) = PatchDMap <$> DMap.traverseWithKey (\k (ComposeMaybe v) -> ComposeMaybe <$> traverse (f k) v) p
57−
58−-- | Weaken a @'PatchDMap' k v@ to a @'PatchMap' (Some k) v'@ using a function
59−-- @v a -> v'@ to weaken each value contained in the patch.
60−weakenPatchDMapWith :: (forall a. v a -> v') -> PatchDMap k v -> PatchMap (Some k) v'
61−weakenPatchDMapWith f (PatchDMap p) = PatchMap $ weakenDMapWith (fmap f . getComposeMaybe) p
62−
63−-- | Convert a weak @'PatchDMap' ('Const2' k a) v@ where the @a@ is known by way of
64−-- the @Const2@ into a @'PatchMap' k v'@ using a rank 1 function @v a -> v'@.
65−patchDMapToPatchMapWith :: (v a -> v') -> PatchDMap (Const2 k a) v -> PatchMap k v'
66−patchDMapToPatchMapWith f (PatchDMap p) = PatchMap $ dmapToMapWith (fmap f . getComposeMaybe) p
67−
68−-- | Convert a @'PatchMap' k v@ into a @'PatchDMap' ('Const2' k a) v'@ using a function @v -> v' a@.
69−const2PatchDMapWith :: forall k v v' a. (v -> v' a) -> PatchMap k v -> PatchDMap (Const2 k a) v'
70−const2PatchDMapWith f (PatchMap p) = PatchDMap $ DMap.fromDistinctAscList $ g <$> Map.toAscList p
71− where g :: (k, Maybe v) -> DSum (Const2 k a) (ComposeMaybe v')
72− g (k, e) = Const2 k :=> ComposeMaybe (f <$> e)
73−
74−-- | Convert a @'PatchIntMap' v@ into a @'PatchDMap' ('Const2' Int a) v'@ using a function @v -> v' a@.
75−const2IntPatchDMapWith :: forall v f a. (v -> f a) -> PatchIntMap v -> PatchDMap (Const2 IntMap.Key a) f
76−const2IntPatchDMapWith f (PatchIntMap p) = PatchDMap $ DMap.fromDistinctAscList $ g <$> IntMap.toAscList p
77− where g :: (IntMap.Key, Maybe v) -> DSum (Const2 IntMap.Key a) (ComposeMaybe f)
78− g (k, e) = Const2 k :=> ComposeMaybe (f <$> e)
79−
80−-- | Get the values that will be replaced or deleted if the given patch is applied to the given 'DMap'.
81−getDeletions :: GCompare k => PatchDMap k v -> DMap k v' -> DMap k v'
82−getDeletions (PatchDMap p) m = DMap.intersectionWithKey (\_ v _ -> v) m p
src/Reflex/Patch/DMapWithMove.hsdeleted−363
@@ -1,363 +0,0 @@
1−{-# LANGUAGE CPP #-}
2−{-# LANGUAGE FlexibleContexts #-}
3−{-# LANGUAGE FlexibleInstances #-}
4−{-# LANGUAGE GADTs #-}
5−{-# LANGUAGE LambdaCase #-}
6−{-# LANGUAGE MultiParamTypeClasses #-}
7−{-# LANGUAGE PatternGuards #-}
8−{-# LANGUAGE PolyKinds #-}
9−{-# LANGUAGE Rank2Types #-}
10−{-# LANGUAGE ScopedTypeVariables #-}
11−{-# LANGUAGE TypeFamilies #-}
12−{-# LANGUAGE UndecidableInstances #-}
13−
14−-- |Module containing @'PatchDMapWithMove' k v@ and associated functions, which represents a 'Patch' to a @'DMap' k v@ which can insert, update, delete, and
15−-- move values between keys.
16−module Reflex.Patch.DMapWithMove where
17−
18−import Reflex.Patch.Class
19−import Reflex.Patch.MapWithMove (PatchMapWithMove (..))
20−import qualified Reflex.Patch.MapWithMove as MapWithMove
21−
22−import Data.Constraint.Extras
23−import Data.Dependent.Map (DMap, DSum (..), GCompare (..))
24−import qualified Data.Dependent.Map as DMap
25−import Data.Functor.Constant
26−import Data.Functor.Misc
27−import Data.Functor.Product
28−import Data.GADT.Compare (GEq (..))
29−import Data.GADT.Show (GShow, gshow)
30−import qualified Data.Map as Map
31−import Data.Maybe
32−import Data.Semigroup (Semigroup (..), (<>))
33−import Data.Some (Some(Some))
34−import Data.These
35−
36−-- | Like 'PatchMapWithMove', but for 'DMap'. Each key carries a 'NodeInfo' which describes how it will be changed by the patch and connects move sources and
37−-- destinations.
38−--
39−-- Invariants:
40−--
41−-- * A key should not move to itself.
42−-- * A move should always be represented with both the destination key (as a 'From_Move') and the source key (as a @'ComposeMaybe' ('Just' destination)@)
43−newtype PatchDMapWithMove k v = PatchDMapWithMove (DMap k (NodeInfo k v))
44−
45−-- |Structure which represents what changes apply to a particular key. @_nodeInfo_from@ specifies what happens to this key, and in particular what other key
46−-- the current key is moving from, while @_nodeInfo_to@ specifies what key the current key is moving to if involved in a move.
47−data NodeInfo k v a = NodeInfo
48− { _nodeInfo_from :: !(From k v a)
49− -- ^Change applying to the current key, be it an insert, move, or delete.
50− , _nodeInfo_to :: !(To k a)
51− -- ^Where this key is moving to, if involved in a move. Should only be @ComposeMaybe (Just k)@ when there is a corresponding 'From_Move'.
52− }
53− deriving (Show)
54−
55−-- |Structure describing a particular change to a key, be it inserting a new key (@From_Insert@), updating an existing key (@From_Insert@ again), deleting a
56−-- key (@From_Delete@), or moving a key (@From_Move@).
57−data From (k :: a -> *) (v :: a -> *) :: a -> * where
58− -- |Insert a new or update an existing key with the given value @v a@
59− From_Insert :: v a -> From k v a
60− -- |Delete the existing key
61− From_Delete :: From k v a
62− -- |Move the value from the given key @k a@ to this key. The source key should also have an entry in the patch giving the current key as @_nodeInfo_to@,
63− -- usually but not necessarily with @From_Delete@.
64− From_Move :: !(k a) -> From k v a
65− deriving (Show, Read, Eq, Ord)
66−
67−-- |Type alias for the "to" part of a 'NodeInfo'. @'ComposeMaybe' ('Just' k)@ means the key is moving to another key, @ComposeMaybe Nothing@ for any other
68−-- operation.
69−type To = ComposeMaybe
70−
71−-- |Test whether a 'PatchDMapWithMove' satisfies its invariants.
72−validPatchDMapWithMove :: forall k v. (GCompare k, GShow k) => DMap k (NodeInfo k v) -> Bool
73−validPatchDMapWithMove = not . null . validationErrorsForPatchDMapWithMove
74−
75−-- |Enumerate what reasons a 'PatchDMapWithMove' doesn't satisfy its invariants, returning @[]@ if it's valid.
76−validationErrorsForPatchDMapWithMove :: forall k v. (GCompare k, GShow k) => DMap k (NodeInfo k v) -> [String]
77−validationErrorsForPatchDMapWithMove m =
78− noSelfMoves <> movesBalanced
79− where
80− noSelfMoves = mapMaybe selfMove . DMap.toAscList $ m
81− selfMove (dst :=> NodeInfo (From_Move src) _) | Just _ <- dst `geq` src = Just $ "self move of key " <> gshow src <> " at destination side"
82− selfMove (src :=> NodeInfo _ (ComposeMaybe (Just dst))) | Just _ <- src `geq` dst = Just $ "self move of key " <> gshow dst <> " at source side"
83− selfMove _ = Nothing
84− movesBalanced = mapMaybe unbalancedMove . DMap.toAscList $ m
85− unbalancedMove (dst :=> NodeInfo (From_Move src) _) =
86− case DMap.lookup src m of
87− Nothing -> Just $ "unbalanced move at destination key " <> gshow dst <> " supposedly from " <> gshow src <> " but source key is not in the patch"
88− Just (NodeInfo _ (ComposeMaybe (Just dst'))) ->
89− if isNothing (dst' `geq` dst)
90− then Just $ "unbalanced move at destination key " <> gshow dst <> " from " <> gshow src <> " is going to " <> gshow dst' <> " instead"
91− else Nothing
92− _ ->
93− Just $ "unbalanced move at destination key " <> gshow dst <> " supposedly from " <> gshow src <> " but source key has no move to key"
94− unbalancedMove (src :=> NodeInfo _ (ComposeMaybe (Just dst))) =
95− case DMap.lookup dst m of
96− Nothing -> Just $ " unbalanced move at source key " <> gshow src <> " supposedly going to " <> gshow dst <> " but destination key is not in the patch"
97− Just (NodeInfo (From_Move src') _) ->
98− if isNothing (src' `geq` src)
99− then Just $ "unbalanced move at source key " <> gshow src <> " to " <> gshow dst <> " is coming from " <> gshow src' <> " instead"
100− else Nothing
101−
102− _ ->
103− Just $ "unbalanced move at source key " <> gshow src <> " supposedly going to " <> gshow dst <> " but destination key is not moving"
104− unbalancedMove _ = Nothing
105−
106−-- |Test whether two @'PatchDMapWithMove' k v@ contain the same patch operations.
107−instance (GEq k, Has' Eq k (NodeInfo k v)) => Eq (PatchDMapWithMove k v) where
108− PatchDMapWithMove a == PatchDMapWithMove b = a == b
109−
110−-- |Higher kinded 2-tuple, identical to @Data.Functor.Product@ from base ≥ 4.9
111−data Pair1 f g a = Pair1 (f a) (g a)
112−
113−-- |Helper data structure used for composing patches using the monoid instance.
114−data Fixup k v a
115− = Fixup_Delete
116− | Fixup_Update (These (From k v a) (To k a))
117−
118−-- |Compose patches having the same effect as applying the patches in turn: @'applyAlways' (p <> q) == 'applyAlways' p . 'applyAlways' q@
119−instance GCompare k => Semigroup (PatchDMapWithMove k v) where
120− PatchDMapWithMove ma <> PatchDMapWithMove mb = PatchDMapWithMove m
121− where
122− connections = DMap.toList $ DMap.intersectionWithKey (\_ a b -> Pair1 (_nodeInfo_to a) (_nodeInfo_from b)) ma mb
123− h :: DSum k (Pair1 (ComposeMaybe k) (From k v)) -> [DSum k (Fixup k v)]
124− h (_ :=> Pair1 (ComposeMaybe mToAfter) editBefore) = case (mToAfter, editBefore) of
125− (Just toAfter, From_Move fromBefore)
126− | isJust $ fromBefore `geq` toAfter
127− -> [toAfter :=> Fixup_Delete]
128− | otherwise
129− -> [ toAfter :=> Fixup_Update (This editBefore)
130− , fromBefore :=> Fixup_Update (That (ComposeMaybe mToAfter))
131− ]
132− (Nothing, From_Move fromBefore) -> [fromBefore :=> Fixup_Update (That (ComposeMaybe mToAfter))] -- The item is destroyed in the second patch, so indicate that it is destroyed in the source map
133− (Just toAfter, _) -> [toAfter :=> Fixup_Update (This editBefore)]
134− (Nothing, _) -> []
135− mergeFixups _ Fixup_Delete Fixup_Delete = Fixup_Delete
136− mergeFixups _ (Fixup_Update a) (Fixup_Update b)
137− | This x <- a, That y <- b
138− = Fixup_Update $ These x y
139− | That y <- a, This x <- b
140− = Fixup_Update $ These x y
141− mergeFixups _ _ _ = error "PatchDMapWithMove: incompatible fixups"
142− fixups = DMap.fromListWithKey mergeFixups $ concatMap h connections
143− combineNodeInfos _ nia nib = NodeInfo
144− { _nodeInfo_from = _nodeInfo_from nia
145− , _nodeInfo_to = _nodeInfo_to nib
146− }
147− applyFixup _ ni = \case
148− Fixup_Delete -> Nothing
149− Fixup_Update u -> Just $ NodeInfo
150− { _nodeInfo_from = fromMaybe (_nodeInfo_from ni) $ getHere u
151− , _nodeInfo_to = fromMaybe (_nodeInfo_to ni) $ getThere u
152− }
153− m = DMap.differenceWithKey applyFixup (DMap.unionWithKey combineNodeInfos ma mb) fixups
154− getHere :: These a b -> Maybe a
155− getHere = \case
156− This a -> Just a
157− These a _ -> Just a
158− That _ -> Nothing
159− getThere :: These a b -> Maybe b
160− getThere = \case
161− This _ -> Nothing
162− These _ b -> Just b
163− That b -> Just b
164−
165−-- |Compose patches having the same effect as applying the patches in turn: @'applyAlways' (p <> q) == 'applyAlways' p . 'applyAlways' q@
166−instance GCompare k => Monoid (PatchDMapWithMove k v) where
167− mempty = PatchDMapWithMove mempty
168− mappend = (<>)
169−
170−{-
171−mappendPatchDMapWithMoveSlow :: forall k v. (ShowTag k v, GCompare k) => PatchDMapWithMove k v -> PatchDMapWithMove k v -> PatchDMapWithMove k v
172−PatchDMapWithMove dstAfter srcAfter `mappendPatchDMapWithMoveSlow` PatchDMapWithMove dstBefore srcBefore = PatchDMapWithMove dst src
173− where
174− getDstAction k m = fromMaybe (From_Move k) $ DMap.lookup k m -- Any key that isn't present is treated as that key moving to itself
175− removeRedundantDst toKey (From_Move fromKey) | isJust (toKey `geq` fromKey) = Nothing
176− removeRedundantDst _ a = Just a
177− f :: forall a. k a -> From k v a -> Maybe (From k v a)
178− f toKey _ = removeRedundantDst toKey $ case getDstAction toKey dstAfter of
179− From_Move fromKey -> getDstAction fromKey dstBefore
180− nonMove -> nonMove
181− dst = DMap.mapMaybeWithKey f $ DMap.union dstAfter dstBefore
182− getSrcAction k m = fromMaybe (ComposeMaybe $ Just k) $ DMap.lookup k m
183− removeRedundantSrc fromKey (ComposeMaybe (Just toKey)) | isJust (fromKey `geq` toKey) = Nothing
184− removeRedundantSrc _ a = Just a
185− g :: forall a. k a -> ComposeMaybe k a -> Maybe (ComposeMaybe k a)
186− g fromKey _ = removeRedundantSrc fromKey $ case getSrcAction fromKey srcBefore of
187− ComposeMaybe Nothing -> ComposeMaybe Nothing
188− ComposeMaybe (Just toKeyBefore) -> getSrcAction toKeyBefore srcAfter
189− src = DMap.mapMaybeWithKey g $ DMap.union srcAfter srcBefore
190−-}
191−
192−-- |Make a @'PatchDMapWithMove' k v@ which has the effect of inserting or updating a value @v a@ to the given key @k a@, like 'DMap.insert'.
193−insertDMapKey :: k a -> v a -> PatchDMapWithMove k v
194−insertDMapKey k v =
195− PatchDMapWithMove . DMap.singleton k $ NodeInfo (From_Insert v) (ComposeMaybe Nothing)
196−
197−-- |Make a @'PatchDMapWithMove' k v@ which has the effect of moving the value from the first key @k a@ to the second key @k a@, equivalent to:
198−--
199−-- @
200−-- 'DMap.delete' src (maybe dmap ('DMap.insert' dst) (DMap.lookup src dmap))
201−-- @
202−moveDMapKey :: GCompare k => k a -> k a -> PatchDMapWithMove k v
203−moveDMapKey src dst = case src `geq` dst of
204− Nothing -> PatchDMapWithMove $ DMap.fromList
205− [ dst :=> NodeInfo (From_Move src) (ComposeMaybe Nothing)
206− , src :=> NodeInfo From_Delete (ComposeMaybe $ Just dst)
207− ]
208− Just _ -> mempty
209−
210−-- |Make a @'PatchDMapWithMove' k v@ which has the effect of swapping two keys in the mapping, equivalent to:
211−--
212−-- @
213−-- let aMay = DMap.lookup a dmap
214−-- bMay = DMap.lookup b dmap
215−-- in maybe id (DMap.insert a) (bMay `mplus` aMay)
216−-- . maybe id (DMap.insert b) (aMay `mplus` bMay)
217−-- . DMap.delete a . DMap.delete b $ dmap
218−-- @
219−swapDMapKey :: GCompare k => k a -> k a -> PatchDMapWithMove k v
220−swapDMapKey src dst = case src `geq` dst of
221− Nothing -> PatchDMapWithMove $ DMap.fromList
222− [ dst :=> NodeInfo (From_Move src) (ComposeMaybe $ Just src)
223− , src :=> NodeInfo (From_Move dst) (ComposeMaybe $ Just dst)
224− ]
225− Just _ -> mempty
226−
227−-- |Make a @'PatchDMapWithMove' k v@ which has the effect of deleting a key in the mapping, equivalent to 'DMap.delete'.
228−deleteDMapKey :: k a -> PatchDMapWithMove k v
229−deleteDMapKey k = PatchDMapWithMove $ DMap.singleton k $ NodeInfo From_Delete $ ComposeMaybe Nothing
230−
231−{-
232−k1, k2 :: Const2 Int () ()
233−k1 = Const2 1
234−k2 = Const2 2
235−p1, p2 :: PatchDMapWithMove (Const2 Int ()) Identity
236−p1 = moveDMapKey k1 k2
237−p2 = moveDMapKey k2 k1
238−p12 = p1 <> p2
239−p21 = p2 <> p1
240−p12Slow = p1 `mappendPatchDMapWithMoveSlow` p2
241−p21Slow = p2 `mappendPatchDMapWithMoveSlow` p1
242−
243−testPatchDMapWithMove = do
244− print p1
245− print p2
246− print $ p12 == deleteDMapKey k1
247− print $ p21 == deleteDMapKey k2
248− print $ p12Slow == deleteDMapKey k1
249− print $ p21Slow == deleteDMapKey k2
250−
251−dst (PatchDMapWithMove x _) = x
252−src (PatchDMapWithMove _ x) = x
253−-}
254−
255−-- |Extract the 'DMap' representing the patch changes from the 'PatchDMapWithMove'.
256−unPatchDMapWithMove :: PatchDMapWithMove k v -> DMap k (NodeInfo k v)
257−unPatchDMapWithMove (PatchDMapWithMove p) = p
258−
259−-- |Wrap a 'DMap' representing patch changes into a 'PatchDMapWithMove', without checking any invariants.
260−--
261−-- __Warning:__ when using this function, you must ensure that the invariants of 'PatchDMapWithMove' are preserved; they will not be checked.
262−unsafePatchDMapWithMove :: DMap k (NodeInfo k v) -> PatchDMapWithMove k v
263−unsafePatchDMapWithMove = PatchDMapWithMove
264−
265−-- |Wrap a 'DMap' representing patch changes into a 'PatchDMapWithMove' while checking invariants. If the invariants are satisfied, @Right p@ is returned
266−-- otherwise @Left errors@.
267−patchDMapWithMove :: (GCompare k, GShow k) => DMap k (NodeInfo k v) -> Either [String] (PatchDMapWithMove k v)
268−patchDMapWithMove dm =
269− case validationErrorsForPatchDMapWithMove dm of
270− [] -> Right $ unsafePatchDMapWithMove dm
271− errs -> Left errs
272−
273−-- |Map a natural transform @v -> v'@ over the given patch, transforming @'PatchDMapWithMove' k v@ into @'PatchDMapWithMove' k v'@.
274−mapPatchDMapWithMove :: forall k v v'. (forall a. v a -> v' a) -> PatchDMapWithMove k v -> PatchDMapWithMove k v'
275−mapPatchDMapWithMove f (PatchDMapWithMove p) = PatchDMapWithMove $
276− DMap.map (\ni -> ni { _nodeInfo_from = g $ _nodeInfo_from ni }) p
277− where g :: forall a. From k v a -> From k v' a
278− g = \case
279− From_Insert v -> From_Insert $ f v
280− From_Delete -> From_Delete
281− From_Move k -> From_Move k
282−
283−-- |Traverse an effectful function @forall a. v a -> m (v ' a)@ over the given patch, transforming @'PatchDMapWithMove' k v@ into @m ('PatchDMapWithMove' k v')@.
284−traversePatchDMapWithMove :: forall m k v v'. Applicative m => (forall a. v a -> m (v' a)) -> PatchDMapWithMove k v -> m (PatchDMapWithMove k v')
285−traversePatchDMapWithMove f = traversePatchDMapWithMoveWithKey $ const f
286−
287−-- |Map an effectful function @forall a. k a -> v a -> m (v ' a)@ over the given patch, transforming @'PatchDMapWithMove' k v@ into @m ('PatchDMapWithMove' k v')@.
288−traversePatchDMapWithMoveWithKey :: forall m k v v'. Applicative m => (forall a. k a -> v a -> m (v' a)) -> PatchDMapWithMove k v -> m (PatchDMapWithMove k v')
289−traversePatchDMapWithMoveWithKey f (PatchDMapWithMove p) = PatchDMapWithMove <$> DMap.traverseWithKey (nodeInfoMapFromM . g) p
290− where g :: forall a. k a -> From k v a -> m (From k v' a)
291− g k = \case
292− From_Insert v -> From_Insert <$> f k v
293− From_Delete -> pure From_Delete
294− From_Move fromKey -> pure $ From_Move fromKey
295−
296−-- |Map a function which transforms @'From' k v a@ into a @'From' k v' a@ over a @'NodeInfo' k v a@.
297−nodeInfoMapFrom :: (From k v a -> From k v' a) -> NodeInfo k v a -> NodeInfo k v' a
298−nodeInfoMapFrom f ni = ni { _nodeInfo_from = f $ _nodeInfo_from ni }
299−
300−-- |Map an effectful function which transforms @'From' k v a@ into a @f ('From' k v' a)@ over a @'NodeInfo' k v a@.
301−nodeInfoMapFromM :: Functor f => (From k v a -> f (From k v' a)) -> NodeInfo k v a -> f (NodeInfo k v' a)
302−nodeInfoMapFromM f ni = fmap (\result -> ni { _nodeInfo_from = result }) $ f $ _nodeInfo_from ni
303−
304−-- |Weaken a 'PatchDMapWithMove' to a 'PatchMapWithMove' by weakening the keys from @k a@ to @'Some' k@ and applying a given weakening function @v a -> v'@ to
305−-- values.
306−weakenPatchDMapWithMoveWith :: forall k v v'. (forall a. v a -> v') -> PatchDMapWithMove k v -> PatchMapWithMove (Some k) v'
307−weakenPatchDMapWithMoveWith f (PatchDMapWithMove p) = PatchMapWithMove $ weakenDMapWith g p
308− where g :: forall a. NodeInfo k v a -> MapWithMove.NodeInfo (Some k) v'
309− g ni = MapWithMove.NodeInfo
310− { MapWithMove._nodeInfo_from = case _nodeInfo_from ni of
311− From_Insert v -> MapWithMove.From_Insert $ f v
312− From_Delete -> MapWithMove.From_Delete
313− From_Move k -> MapWithMove.From_Move $ Some k
314− , MapWithMove._nodeInfo_to = Some <$> getComposeMaybe (_nodeInfo_to ni)
315− }
316−
317−-- |"Weaken" a @'PatchDMapWithMove' (Const2 k a) v@ to a @'PatchMapWithMove' k v'@. Weaken is in scare quotes because the 'Const2' has already disabled any
318−-- dependency in the typing and all points are already @a@, hence the function to map each value to @v'@ is not higher rank.
319−patchDMapWithMoveToPatchMapWithMoveWith :: forall k v v' a. (v a -> v') -> PatchDMapWithMove (Const2 k a) v -> PatchMapWithMove k v'
320−patchDMapWithMoveToPatchMapWithMoveWith f (PatchDMapWithMove p) = PatchMapWithMove $ dmapToMapWith g p
321− where g :: NodeInfo (Const2 k a) v a -> MapWithMove.NodeInfo k v'
322− g ni = MapWithMove.NodeInfo
323− { MapWithMove._nodeInfo_from = case _nodeInfo_from ni of
324− From_Insert v -> MapWithMove.From_Insert $ f v
325− From_Delete -> MapWithMove.From_Delete
326− From_Move (Const2 k) -> MapWithMove.From_Move k
327− , MapWithMove._nodeInfo_to = unConst2 <$> getComposeMaybe (_nodeInfo_to ni)
328− }
329−
330−-- |"Strengthen" a @'PatchMapWithMove' k v@ into a @'PatchDMapWithMove ('Const2' k a)@; that is, turn a non-dependently-typed patch into a dependently typed
331−-- one but which always has a constant key type represented by 'Const2'. Apply the given function to each @v@ to produce a @v' a@.
332−-- Completemented by 'patchDMapWithMoveToPatchMapWithMoveWith'
333−const2PatchDMapWithMoveWith :: forall k v v' a. (v -> v' a) -> PatchMapWithMove k v -> PatchDMapWithMove (Const2 k a) v'
334−const2PatchDMapWithMoveWith f (PatchMapWithMove p) = PatchDMapWithMove $ DMap.fromDistinctAscList $ g <$> Map.toAscList p
335− where g :: (k, MapWithMove.NodeInfo k v) -> DSum (Const2 k a) (NodeInfo (Const2 k a) v')
336− g (k, ni) = Const2 k :=> NodeInfo
337− { _nodeInfo_from = case MapWithMove._nodeInfo_from ni of
338− MapWithMove.From_Insert v -> From_Insert $ f v
339− MapWithMove.From_Delete -> From_Delete
340− MapWithMove.From_Move fromKey -> From_Move $ Const2 fromKey
341− , _nodeInfo_to = ComposeMaybe $ Const2 <$> MapWithMove._nodeInfo_to ni
342− }
343−
344−-- | Apply the insertions, deletions, and moves to a given 'DMap'.
345−instance GCompare k => Patch (PatchDMapWithMove k v) where
346− type PatchTarget (PatchDMapWithMove k v) = DMap k v
347− apply (PatchDMapWithMove p) old = Just $! insertions `DMap.union` (old `DMap.difference` deletions) --TODO: return Nothing sometimes --Note: the strict application here is critical to ensuring that incremental merges don't hold onto all their prerequisite events forever; can we make this more robust?
348− where insertions = DMap.mapMaybeWithKey insertFunc p
349− insertFunc :: forall a. k a -> NodeInfo k v a -> Maybe (v a)
350− insertFunc _ ni = case _nodeInfo_from ni of
351− From_Insert v -> Just v
352− From_Move k -> DMap.lookup k old
353− From_Delete -> Nothing
354− deletions = DMap.mapMaybeWithKey deleteFunc p
355− deleteFunc :: forall a. k a -> NodeInfo k v a -> Maybe (Constant () a)
356− deleteFunc _ ni = case _nodeInfo_from ni of
357− From_Delete -> Just $ Constant ()
358− _ -> Nothing
359−
360−-- | Get the values that will be replaced, deleted, or moved if the given patch is applied to the given 'DMap'.
361−getDeletionsAndMoves :: GCompare k => PatchDMapWithMove k v -> DMap k v' -> DMap k (Product v' (ComposeMaybe k))
362−getDeletionsAndMoves (PatchDMapWithMove p) m = DMap.intersectionWithKey f m p
363− where f _ v ni = Pair v $ _nodeInfo_to ni
src/Reflex/Patch/IntMap.hsdeleted−59
@@ -1,59 +0,0 @@
1−{-# LANGUAGE DeriveFoldable #-}
2−{-# LANGUAGE DeriveFunctor #-}
3−{-# LANGUAGE DeriveTraversable #-}
4−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5−{-# LANGUAGE TypeFamilies #-}
6−-- | Module containing 'PatchIntMap', a 'Patch' for 'IntMap' which allows for
7−-- insert/update or delete of associations.
8−module Reflex.Patch.IntMap where
9−
10−import Data.IntMap.Strict (IntMap)
11−import qualified Data.IntMap.Strict as IntMap
12−import Data.Maybe
13−import Data.Semigroup
14−import Reflex.Patch.Class
15−
16−-- | 'Patch' for 'IntMap' which represents insertion or deletion of keys in the mapping.
17−-- Internally represented by 'IntMap (Maybe a)', where @Just@ means insert/update
18−-- and @Nothing@ means delete.
19−newtype PatchIntMap a = PatchIntMap (IntMap (Maybe a)) deriving (Functor, Foldable, Traversable, Monoid)
20−
21−-- | Apply the insertions or deletions to a given 'IntMap'.
22−instance Patch (PatchIntMap a) where
23− type PatchTarget (PatchIntMap a) = IntMap a
24− apply (PatchIntMap p) v = if IntMap.null p then Nothing else Just $
25− let removes = IntMap.filter isNothing p
26− adds = IntMap.mapMaybe id p
27− in IntMap.union adds $ v `IntMap.difference` removes
28−
29−-- | @a <> b@ will apply the changes of @b@ and then apply the changes of @a@.
30−-- If the same key is modified by both patches, the one on the left will take
31−-- precedence.
32−instance Semigroup (PatchIntMap v) where
33− PatchIntMap a <> PatchIntMap b = PatchIntMap $ a `mappend` b --TODO: Add a semigroup instance for Map
34− -- PatchMap is idempotent, so stimes n is id for every n
35− stimes = stimesIdempotentMonoid
36−
37−-- | Map a function @Int -> a -> b@ over all @a@s in the given @'PatchIntMap' a@
38−-- (that is, all inserts/updates), producing a @PatchIntMap b@.
39−mapIntMapPatchWithKey :: (Int -> a -> b) -> PatchIntMap a -> PatchIntMap b
40−mapIntMapPatchWithKey f (PatchIntMap m) = PatchIntMap $ IntMap.mapWithKey (\ k mv -> f k <$> mv) m
41−
42−-- | Map an effectful function @Int -> a -> f b@ over all @a@s in the given @'PatchIntMap' a@
43−-- (that is, all inserts/updates), producing a @f (PatchIntMap b)@.
44−traverseIntMapPatchWithKey :: Applicative f => (Int -> a -> f b) -> PatchIntMap a -> f (PatchIntMap b)
45−traverseIntMapPatchWithKey f (PatchIntMap m) = PatchIntMap <$> IntMap.traverseWithKey (\k mv -> traverse (f k) mv) m
46−
47−-- | Extract all @a@s inserted/updated by the given @'PatchIntMap' a@.
48−patchIntMapNewElements :: PatchIntMap a -> [a]
49−patchIntMapNewElements (PatchIntMap m) = catMaybes $ IntMap.elems m
50−
51−-- | Convert the given @'PatchIntMap' a@ into an @'IntMap' a@ with all
52−-- the inserts/updates in the given patch.
53−patchIntMapNewElementsMap :: PatchIntMap a -> IntMap a
54−patchIntMapNewElementsMap (PatchIntMap m) = IntMap.mapMaybe id m
55−
56−-- | Subset the given @'IntMap' a@ to contain only the keys that would be
57−-- deleted by the given @'PatchIntMap' a@.
58−getDeletions :: PatchIntMap v -> IntMap v' -> IntMap v'
59−getDeletions (PatchIntMap m) v = IntMap.intersection v m
src/Reflex/Patch/Map.hsdeleted−55
@@ -1,55 +0,0 @@
1−{-# LANGUAGE LambdaCase #-}
2−{-# LANGUAGE TypeFamilies #-}
3−-- | 'Patch'es on 'Map' that consist only of insertions (including overwrites)
4−-- and deletions
5−module Reflex.Patch.Map where
6−
7−import Reflex.Patch.Class
8−
9−import Data.Map (Map)
10−import qualified Data.Map as Map
11−import Data.Maybe
12−import Data.Semigroup
13−
14−-- | A set of changes to a 'Map'. Any element may be inserted/updated or
15−-- deleted. Insertions are represented as values wrapped in 'Just', while
16−-- deletions are represented as 'Nothing's
17−newtype PatchMap k v = PatchMap { unPatchMap :: Map k (Maybe v) }
18− deriving (Show, Read, Eq, Ord)
19−
20−-- | Apply the insertions or deletions to a given 'Map'.
21−instance Ord k => Patch (PatchMap k v) where
22− type PatchTarget (PatchMap k v) = Map k v
23− {-# INLINABLE apply #-}
24− apply (PatchMap p) old = Just $! insertions `Map.union` (old `Map.difference` deletions) --TODO: return Nothing sometimes --Note: the strict application here is critical to ensuring that incremental merges don't hold onto all their prerequisite events forever; can we make this more robust?
25− where insertions = Map.mapMaybeWithKey (const id) p
26− deletions = Map.mapMaybeWithKey (const nothingToJust) p
27− nothingToJust = \case
28− Nothing -> Just ()
29− Just _ -> Nothing
30−
31−-- | @a <> b@ will apply the changes of @b@ and then apply the changes of @a@.
32−-- If the same key is modified by both patches, the one on the left will take
33−-- precedence.
34−instance Ord k => Semigroup (PatchMap k v) where
35− PatchMap a <> PatchMap b = PatchMap $ a `mappend` b --TODO: Add a semigroup instance for Map
36− -- PatchMap is idempotent, so stimes n is id for every n
37− stimes = stimesIdempotentMonoid
38−
39−-- | The empty 'PatchMap' contains no insertions or deletions
40−instance Ord k => Monoid (PatchMap k v) where
41− mempty = PatchMap mempty
42− mappend = (<>)
43−
44−-- | 'fmap'ping a 'PatchMap' will alter all of the values it will insert.
45−-- Deletions are unaffected.
46−instance Functor (PatchMap k) where
47− fmap f = PatchMap . fmap (fmap f) . unPatchMap
48−
49−-- | Returns all the new elements that will be added to the 'Map'
50−patchMapNewElements :: PatchMap k v -> [v]
51−patchMapNewElements (PatchMap p) = catMaybes $ Map.elems p
52−
53−-- | Returns all the new elements that will be added to the 'Map'
54−patchMapNewElementsMap :: PatchMap k v -> Map k v
55−patchMapNewElementsMap (PatchMap p) = Map.mapMaybe id p
src/Reflex/Patch/MapWithMove.hsdeleted−270
@@ -1,270 +0,0 @@
1−{-# LANGUAGE DeriveFoldable #-}
2−{-# LANGUAGE DeriveFunctor #-}
3−{-# LANGUAGE DeriveTraversable #-}
4−{-# LANGUAGE FlexibleContexts #-}
5−{-# LANGUAGE LambdaCase #-}
6−{-# LANGUAGE PatternGuards #-}
7−{-# LANGUAGE ScopedTypeVariables #-}
8−{-# LANGUAGE TypeFamilies #-}
9−-- | 'Patch'es on 'Map' that can insert, delete, and move values from one key to
10−-- another
11−module Reflex.Patch.MapWithMove where
12−
13−import Reflex.Patch.Class
14−
15−import Control.Arrow
16−import Control.Monad.State
17−import Data.Foldable
18−import Data.Function
19−import Data.List
20−import Data.Map (Map)
21−import qualified Data.Map as Map
22−import Data.Maybe
23−import Data.Semigroup (Semigroup (..), (<>))
24−import qualified Data.Set as Set
25−import Data.These (These(..))
26−import Data.Tuple
27−
28−-- | Patch a DMap with additions, deletions, and moves. Invariant: If key @k1@
29−-- is coming from @From_Move k2@, then key @k2@ should be going to @Just k1@,
30−-- and vice versa. There should never be any unpaired From/To keys.
31−newtype PatchMapWithMove k v = PatchMapWithMove (Map k (NodeInfo k v)) deriving (Show, Eq, Ord, Functor, Foldable, Traversable)
32−
33−-- | Holds the information about each key: where its new value should come from,
34−-- and where its old value should go to
35−data NodeInfo k v = NodeInfo
36− { _nodeInfo_from :: !(From k v)
37− -- ^ Where do we get the new value for this key?
38− , _nodeInfo_to :: !(To k)
39− -- ^ If the old value is being kept (i.e. moved rather than deleted or
40− -- replaced), where is it going?
41− }
42− deriving (Show, Read, Eq, Ord, Functor, Foldable, Traversable)
43−
44−-- | Describe how a key's new value should be produced
45−data From k v
46− = From_Insert v -- ^ Insert the given value here
47− | From_Delete -- ^ Delete the existing value, if any, from here
48− | From_Move !k -- ^ Move the value here from the given key
49− deriving (Show, Read, Eq, Ord, Functor, Foldable, Traversable)
50−
51−-- | Describe where a key's old value will go. If this is 'Just', that means
52−-- the key's old value will be moved to the given other key; if it is 'Nothing',
53−-- that means it will be deleted.
54−type To = Maybe
55−
56−-- | Create a 'PatchMapWithMove', validating it
57−patchMapWithMove :: Ord k => Map k (NodeInfo k v) -> Maybe (PatchMapWithMove k v)
58−patchMapWithMove m = if valid then Just $ PatchMapWithMove m else Nothing
59− where valid = forwardLinks == backwardLinks
60− forwardLinks = Map.mapMaybe _nodeInfo_to m
61− backwardLinks = Map.fromList $ catMaybes $ flip fmap (Map.toList m) $ \(to, v) ->
62− case _nodeInfo_from v of
63− From_Move from -> Just (from, to)
64− _ -> Nothing
65−
66−-- | Create a 'PatchMapWithMove' that inserts everything in the given 'Map'
67−patchMapWithMoveInsertAll :: Map k v -> PatchMapWithMove k v
68−patchMapWithMoveInsertAll m = PatchMapWithMove $ flip fmap m $ \v -> NodeInfo
69− { _nodeInfo_from = From_Insert v
70− , _nodeInfo_to = Nothing
71− }
72−
73−-- | Extract the internal representation of the 'PatchMapWithMove'
74−unPatchMapWithMove :: PatchMapWithMove k v -> Map k (NodeInfo k v)
75−unPatchMapWithMove (PatchMapWithMove p) = p
76−
77−-- | Make a @'PatchMapWithMove' k v@ which has the effect of inserting or updating a value @v@ to the given key @k@, like 'Map.insert'.
78−insertMapKey :: k -> v -> PatchMapWithMove k v
79−insertMapKey k v = PatchMapWithMove . Map.singleton k $ NodeInfo (From_Insert v) Nothing
80−
81−-- |Make a @'PatchMapWithMove' k v@ which has the effect of moving the value from the first key @k@ to the second key @k@, equivalent to:
82−--
83−-- @
84−-- 'Map.delete' src (maybe map ('Map.insert' dst) (Map.lookup src map))
85−-- @
86−moveMapKey :: Ord k => k -> k -> PatchMapWithMove k v
87−moveMapKey src dst
88− | src == dst = mempty
89− | otherwise =
90− PatchMapWithMove $ Map.fromList
91− [ (dst, NodeInfo (From_Move src) Nothing)
92− , (src, NodeInfo From_Delete (Just dst))
93− ]
94−
95−-- |Make a @'PatchMapWithMove' k v@ which has the effect of swapping two keys in the mapping, equivalent to:
96−--
97−-- @
98−-- let aMay = Map.lookup a map
99−-- bMay = Map.lookup b map
100−-- in maybe id (Map.insert a) (bMay `mplus` aMay)
101−-- . maybe id (Map.insert b) (aMay `mplus` bMay)
102−-- . Map.delete a . Map.delete b $ map
103−-- @
104−swapMapKey :: Ord k => k -> k -> PatchMapWithMove k v
105−swapMapKey src dst
106− | src == dst = mempty
107− | otherwise =
108− PatchMapWithMove $ Map.fromList
109− [ (dst, NodeInfo (From_Move src) (Just src))
110− , (src, NodeInfo (From_Move dst) (Just dst))
111− ]
112−
113−-- |Make a @'PatchMapWithMove' k v@ which has the effect of deleting a key in the mapping, equivalent to 'Map.delete'.
114−deleteMapKey :: k -> PatchMapWithMove k v
115−deleteMapKey k = PatchMapWithMove . Map.singleton k $ NodeInfo From_Delete Nothing
116−
117−-- | Wrap a @'Map' k (NodeInfo k v)@ representing patch changes into a @'PatchMapWithMove' k v@, without checking any invariants.
118−--
119−-- __Warning:__ when using this function, you must ensure that the invariants of 'PatchMapWithMove' are preserved; they will not be checked.
120−unsafePatchMapWithMove :: Map k (NodeInfo k v) -> PatchMapWithMove k v
121−unsafePatchMapWithMove = PatchMapWithMove
122−
123−-- | Apply the insertions, deletions, and moves to a given 'Map'
124−instance Ord k => Patch (PatchMapWithMove k v) where
125− type PatchTarget (PatchMapWithMove k v) = Map k v
126− apply (PatchMapWithMove p) old = Just $! insertions `Map.union` (old `Map.difference` deletions) --TODO: return Nothing sometimes --Note: the strict application here is critical to ensuring that incremental merges don't hold onto all their prerequisite events forever; can we make this more robust?
127− where insertions = flip Map.mapMaybeWithKey p $ \_ ni -> case _nodeInfo_from ni of
128− From_Insert v -> Just v
129− From_Move k -> Map.lookup k old
130− From_Delete -> Nothing
131− deletions = flip Map.mapMaybeWithKey p $ \_ ni -> case _nodeInfo_from ni of
132− From_Delete -> Just ()
133− _ -> Nothing
134−
135−-- | Returns all the new elements that will be added to the 'Map'.
136−patchMapWithMoveNewElements :: PatchMapWithMove k v -> [v]
137−patchMapWithMoveNewElements = Map.elems . patchMapWithMoveNewElementsMap
138−
139−-- | Return a @'Map' k v@ with all the inserts/updates from the given @'PatchMapWithMove' k v@.
140−patchMapWithMoveNewElementsMap :: PatchMapWithMove k v -> Map k v
141−patchMapWithMoveNewElementsMap (PatchMapWithMove p) = Map.mapMaybe f p
142− where f ni = case _nodeInfo_from ni of
143− From_Insert v -> Just v
144− From_Move _ -> Nothing
145− From_Delete -> Nothing
146−
147−-- | Create a 'PatchMapWithMove' that, if applied to the given 'Map', will sort
148−-- its values using the given ordering function. The set keys of the 'Map' is
149−-- not changed.
150−patchThatSortsMapWith :: Ord k => (v -> v -> Ordering) -> Map k v -> PatchMapWithMove k v
151−patchThatSortsMapWith cmp m = PatchMapWithMove $ Map.fromList $ catMaybes $ zipWith g unsorted sorted
152− where unsorted = Map.toList m
153− sorted = sortBy (cmp `on` snd) unsorted
154− f (to, _) (from, _) = if to == from then Nothing else
155− Just (from, to)
156− reverseMapping = Map.fromList $ catMaybes $ zipWith f unsorted sorted
157− g (to, _) (from, _) = if to == from then Nothing else
158− let Just movingTo = Map.lookup to reverseMapping
159− in Just (to, NodeInfo (From_Move from) $ Just movingTo)
160−
161−-- | Create a 'PatchMapWithMove' that, if applied to the first 'Map' provided,
162−-- will produce a 'Map' with the same values as the second 'Map' but with the
163−-- values sorted with the given ordering function.
164−patchThatChangesAndSortsMapWith :: forall k v. (Ord k, Ord v) => (v -> v -> Ordering) -> Map k v -> Map k v -> PatchMapWithMove k v
165−patchThatChangesAndSortsMapWith cmp oldByIndex newByIndexUnsorted = patchThatChangesMap oldByIndex newByIndex
166− where newList = Map.toList newByIndexUnsorted
167− newByIndex = Map.fromList $ zip (fst <$> newList) $ sortBy cmp $ snd <$> newList
168−
169−-- | Create a 'PatchMapWithMove' that, if applied to the first 'Map' provided,
170−-- will produce the second 'Map'.
171−patchThatChangesMap :: (Ord k, Ord v) => Map k v -> Map k v -> PatchMapWithMove k v
172−patchThatChangesMap oldByIndex newByIndex = patch
173− where oldByValue = Map.fromListWith Set.union $ swap . first Set.singleton <$> Map.toList oldByIndex
174− (insertsAndMoves, unusedValuesByValue) = flip runState oldByValue $ do
175− let f k v = do
176− remainingValues <- get
177− let putRemainingKeys remainingKeys = put $ if Set.null remainingKeys
178− then Map.delete v remainingValues
179− else Map.insert v remainingKeys remainingValues
180− case Map.lookup v remainingValues of
181− Nothing -> return $ NodeInfo (From_Insert v) $ Just undefined -- There's no existing value we can take
182− Just fromKs ->
183− if k `Set.member` fromKs
184− then do
185− putRemainingKeys $ Set.delete k fromKs
186− return $ NodeInfo (From_Move k) $ Just undefined -- There's an existing value, and it's here, so no patch necessary
187− else do
188− (fromK, remainingKeys) <- return . fromJust $ Set.minView fromKs -- There's an existing value, but it's not here; move it here
189− putRemainingKeys remainingKeys
190− return $ NodeInfo (From_Move fromK) $ Just undefined
191− Map.traverseWithKey f newByIndex
192− unusedOldKeys = fold unusedValuesByValue
193− pointlessMove k = \case
194− From_Move k' | k == k' -> True
195− _ -> False
196− keyWasMoved k = if k `Map.member` oldByIndex && not (k `Set.member` unusedOldKeys)
197− then Just undefined
198− else Nothing
199− patch = unsafePatchMapWithMove $ Map.filterWithKey (\k -> not . pointlessMove k . _nodeInfo_from) $ Map.mergeWithKey (\k a _ -> Just $ nodeInfoSetTo (keyWasMoved k) a) (Map.mapWithKey $ \k -> nodeInfoSetTo $ keyWasMoved k) (Map.mapWithKey $ \k _ -> NodeInfo From_Delete $ keyWasMoved k) insertsAndMoves oldByIndex
200−
201−-- | Change the 'From' value of a 'NodeInfo'
202−nodeInfoMapFrom :: (From k v -> From k v) -> NodeInfo k v -> NodeInfo k v
203−nodeInfoMapFrom f ni = ni { _nodeInfo_from = f $ _nodeInfo_from ni }
204−
205−-- | Change the 'From' value of a 'NodeInfo', using a 'Functor' (or
206−-- 'Applicative', 'Monad', etc.) action to get the new value
207−nodeInfoMapMFrom :: Functor f => (From k v -> f (From k v)) -> NodeInfo k v -> f (NodeInfo k v)
208−nodeInfoMapMFrom f ni = fmap (\result -> ni { _nodeInfo_from = result }) $ f $ _nodeInfo_from ni
209−
210−-- | Set the 'To' field of a 'NodeInfo'
211−nodeInfoSetTo :: To k -> NodeInfo k v -> NodeInfo k v
212−nodeInfoSetTo to ni = ni { _nodeInfo_to = to }
213−
214−-- |Helper data structure used for composing patches using the monoid instance.
215−data Fixup k v
216− = Fixup_Delete
217− | Fixup_Update (These (From k v) (To k))
218−
219−-- |Compose patches having the same effect as applying the patches in turn: @'applyAlways' (p <> q) == 'applyAlways' p . 'applyAlways' q@
220−instance Ord k => Semigroup (PatchMapWithMove k v) where
221− PatchMapWithMove ma <> PatchMapWithMove mb = PatchMapWithMove m
222− where
223− connections = Map.toList $ Map.intersectionWithKey (\_ a b -> (_nodeInfo_to a, _nodeInfo_from b)) ma mb
224− h :: (k, (Maybe k, From k v)) -> [(k, Fixup k v)]
225− h (_, (mToAfter, editBefore)) = case (mToAfter, editBefore) of
226− (Just toAfter, From_Move fromBefore)
227− | fromBefore == toAfter
228− -> [(toAfter, Fixup_Delete)]
229− | otherwise
230− -> [ (toAfter, Fixup_Update (This editBefore))
231− , (fromBefore, Fixup_Update (That mToAfter))
232− ]
233− (Nothing, From_Move fromBefore) -> [(fromBefore, Fixup_Update (That mToAfter))] -- The item is destroyed in the second patch, so indicate that it is destroyed in the source map
234− (Just toAfter, _) -> [(toAfter, Fixup_Update (This editBefore))]
235− (Nothing, _) -> []
236− mergeFixups _ Fixup_Delete Fixup_Delete = Fixup_Delete
237− mergeFixups _ (Fixup_Update a) (Fixup_Update b)
238− | This x <- a, That y <- b
239− = Fixup_Update $ These x y
240− | That y <- a, This x <- b
241− = Fixup_Update $ These x y
242− mergeFixups _ _ _ = error "PatchMapWithMove: incompatible fixups"
243− fixups = Map.fromListWithKey mergeFixups $ concatMap h connections
244− combineNodeInfos _ nia nib = NodeInfo
245− { _nodeInfo_from = _nodeInfo_from nia
246− , _nodeInfo_to = _nodeInfo_to nib
247− }
248− applyFixup _ ni = \case
249− Fixup_Delete -> Nothing
250− Fixup_Update u -> Just $ NodeInfo
251− { _nodeInfo_from = fromMaybe (_nodeInfo_from ni) $ getHere u
252− , _nodeInfo_to = fromMaybe (_nodeInfo_to ni) $ getThere u
253− }
254− m = Map.differenceWithKey applyFixup (Map.unionWithKey combineNodeInfos ma mb) fixups
255− getHere :: These a b -> Maybe a
256− getHere = \case
257− This a -> Just a
258− These a _ -> Just a
259− That _ -> Nothing
260− getThere :: These a b -> Maybe b
261− getThere = \case
262− This _ -> Nothing
263− These _ b -> Just b
264− That b -> Just b
265−
266−--TODO: Figure out how to implement this in terms of PatchDMapWithMove rather than duplicating it here
267−-- |Compose patches having the same effect as applying the patches in turn: @'applyAlways' (p <> q) == 'applyAlways' p . 'applyAlways' q@
268−instance Ord k => Monoid (PatchMapWithMove k v) where
269− mempty = PatchMapWithMove mempty
270− mappend = (<>)
src/Reflex/PerformEvent/Class.hs+6
@@ -22,6 +22,7 @@ import Reflex.Class
2222import Reflex.TriggerEvent.Class
2323
2424import Control.Monad.Reader
25+import Control.Monad.Trans.Maybe (MaybeT (..))
2526
2627-- | 'PerformEvent' represents actions that can trigger other actions based on
2728-- 'Event's.
@@ -61,3 +62,8 @@ instance PerformEvent t m => PerformEvent t (ReaderT r m) where
6162 performEvent e = do
6263 r <- ask
6364 lift $ performEvent $ flip runReaderT r <$> e
65+
66+instance PerformEvent t m => PerformEvent t (MaybeT m) where
67+ type Performable (MaybeT m) = MaybeT (Performable m)
68+ performEvent_ = lift . performEvent_ . fmapCheap (void . runMaybeT)
69+ performEvent = lift . fmap (fmapMaybe id) . performEvent . fmapCheap runMaybeT
src/Reflex/Profiled.hs+55−3
@@ -26,6 +26,7 @@ import Control.Monad.Primitive
2626import Control.Monad.Reader
2727import Control.Monad.Ref
2828import Control.Monad.State.Strict (StateT, execStateT, modify)
29+import Data.Bifunctor
2930import Data.Coerce
3031import Data.Dependent.Map (DMap, GCompare)
3132import Data.FastMutableIntMap
@@ -42,12 +43,20 @@ import Foreign.Ptr
4243import GHC.Foreign
4344import GHC.IO.Encoding
4445import GHC.Stack
46+import Reflex.Adjustable.Class
47+import Reflex.BehaviorWriter.Class
4548import Reflex.Class
49+import Reflex.DynamicWriter.Class
50+import Reflex.EventWriter.Class
4651import Reflex.Host.Class
52+import Reflex.NotReady.Class
4753import Reflex.PerformEvent.Class
54+import Reflex.PostBuild.Class
55+import Reflex.Query.Class
56+import Reflex.Requester.Class
57+import Reflex.TriggerEvent.Class
4858
4959import System.IO.Unsafe
50−import Unsafe.Coerce
5160
5261data ProfiledTimeline t
5362
@@ -147,8 +156,8 @@ instance Reflex t => Reflex (ProfiledTimeline t) where
147156 updated (Dynamic_Profiled d) = coerce $ profileEvent $ updated d
148157 unsafeBuildDynamic (ProfiledM a0) (Event_Profiled a') = coerce $ unsafeBuildDynamic a0 a'
149158 unsafeBuildIncremental (ProfiledM a0) (Event_Profiled a') = coerce $ unsafeBuildIncremental a0 a'
150− mergeIncrementalG nt = (Event_Profiled . coerce) #. mergeIncrementalG nt
151− mergeIncrementalWithMoveG nt = (Event_Profiled . coerce) #. mergeIncrementalWithMoveG nt
159+ mergeIncrementalG nt res = Event_Profiled $ mergeIncrementalG (coerce nt) (coerce res)
160+ mergeIncrementalWithMoveG nt res = Event_Profiled $ mergeIncrementalWithMoveG (coerce nt) (coerce res)
152161 currentIncremental (Incremental_Profiled i) = coerce $ currentIncremental i
153162 updatedIncremental (Incremental_Profiled i) = coerce $ profileEvent $ updatedIncremental i
154163 incrementalToDynamic (Incremental_Profiled i) = coerce $ incrementalToDynamic i
@@ -178,6 +187,16 @@ instance MonadHold t m => MonadHold (ProfiledTimeline t) (ProfiledM m) where
178187instance MonadSample t m => MonadSample (ProfiledTimeline t) (ProfiledM m) where
179188 sample (Behavior_Profiled b) = ProfiledM $ sample b
180189
190+instance Adjustable t m => Adjustable (ProfiledTimeline t) (ProfiledM m) where
191+ runWithReplace a0 a' = (fmap . fmap) coerce . lift $
192+ runWithReplace (coerce a0) (coerce $ coerce <$> a')
193+ traverseIntMapWithKeyWithAdjust f dm0 dm' = (fmap . fmap) coerce . lift $
194+ traverseIntMapWithKeyWithAdjust (\k v -> coerce $ f k v) dm0 (coerce dm')
195+ traverseDMapWithKeyWithAdjust f dm0 dm' = (fmap . fmap) coerce . lift $
196+ traverseDMapWithKeyWithAdjust (\k v -> coerce $ f k v) dm0 (coerce dm')
197+ traverseDMapWithKeyWithAdjustWithMove f dm0 dm' = (fmap . fmap) coerce . lift $
198+ traverseDMapWithKeyWithAdjustWithMove (\k v -> coerce $ f k v) dm0 (coerce dm')
199+
181200instance MonadTrans ProfiledM where
182201 lift = ProfiledM
183202
@@ -189,6 +208,39 @@ instance PerformEvent t m => PerformEvent (ProfiledTimeline t) (ProfiledM m) whe
189208 performEvent_ = lift . performEvent_ . coerce
190209 performEvent = lift . fmap coerce . performEvent . coerce
191210
211+instance TriggerEvent t m => TriggerEvent (ProfiledTimeline t) (ProfiledM m) where
212+ newTriggerEvent = first coerce <$> lift newTriggerEvent
213+ newTriggerEventWithOnComplete = first coerce <$> lift newTriggerEventWithOnComplete
214+ newEventWithLazyTriggerWithOnComplete f = coerce <$> lift (newEventWithLazyTriggerWithOnComplete f)
215+
216+instance PostBuild t m => PostBuild (ProfiledTimeline t) (ProfiledM m) where
217+ getPostBuild = coerce <$> lift getPostBuild
218+
219+instance NotReady t m => NotReady (ProfiledTimeline t) (ProfiledM m) where
220+ notReady = lift notReady
221+ notReadyUntil = lift . notReadyUntil . coerce
222+
223+instance BehaviorWriter t w m => BehaviorWriter (ProfiledTimeline t) w (ProfiledM m) where
224+ tellBehavior = lift . tellBehavior . coerce
225+
226+instance DynamicWriter t w m => DynamicWriter (ProfiledTimeline t) w (ProfiledM m) where
227+ tellDyn = lift . tellDyn . coerce
228+
229+instance EventWriter t w m => EventWriter (ProfiledTimeline t) w (ProfiledM m) where
230+ tellEvent = lift . tellEvent . coerce
231+
232+instance MonadQuery t q m => MonadQuery (ProfiledTimeline t) q (ProfiledM m) where
233+ tellQueryIncremental = lift . tellQueryIncremental . coerce
234+ askQueryResult = coerce <$> lift askQueryResult
235+ queryIncremental = fmap coerce . lift . queryIncremental . coerce
236+
237+instance Requester t m => Requester (ProfiledTimeline t) (ProfiledM m) where
238+ type Request (ProfiledM m) = Request m
239+ type Response (ProfiledM m) = Response m
240+
241+ requesting = fmap coerce . lift . requesting . coerce
242+ requesting_ = lift . requesting_ . coerce
243+
192244instance MonadRef m => MonadRef (ProfiledM m) where
193245 type Ref (ProfiledM m) = Ref m
194246 newRef = lift . newRef
src/Reflex/Query/Base.hs+1−1
@@ -47,7 +47,7 @@ import Reflex.DynamicWriter.Class
4747import Reflex.EventWriter.Base
4848import Reflex.EventWriter.Class
4949import Reflex.Host.Class
50−import qualified Reflex.Patch.MapWithMove as MapWithMove
50+import qualified Data.Patch.MapWithMove as MapWithMove
5151import Reflex.PerformEvent.Class
5252import Reflex.PostBuild.Class
5353import Reflex.Query.Class
src/Reflex/Query/Class.hs+44−4
@@ -1,3 +1,4 @@
1+{-# LANGUAGE CPP #-}
12{-# LANGUAGE DeriveDataTypeable #-}
23{-# LANGUAGE FlexibleContexts #-}
34{-# LANGUAGE FlexibleInstances #-}
@@ -21,6 +22,7 @@ module Reflex.Query.Class
2122 , MonadQuery (..)
2223 , tellQueryDyn
2324 , queryDyn
25+ , subQuery
2426 , mapQuery
2527 , mapQueryResult
2628 ) where
@@ -35,6 +37,9 @@ import Data.Map.Monoidal (MonoidalMap)
3537import qualified Data.Map.Monoidal as MonoidalMap
3638import Data.Semigroup (Semigroup(..))
3739import Foreign.Storable
40+import Data.Void
41+import Data.Monoid hiding ((<>))
42+import Control.Applicative
3843
3944import Reflex.Class
4045
@@ -50,6 +55,37 @@ instance (Ord k, Query v) => Query (MonoidalMap k v) where
5055 type QueryResult (MonoidalMap k v) = MonoidalMap k (QueryResult v)
5156 crop q r = MonoidalMap.intersectionWith (flip crop) r q
5257
58+-- | the result of two queries is both results.
59+instance (Query a, Query b) => Query (a, b) where
60+ type QueryResult (a, b) = (QueryResult a, QueryResult b)
61+ crop (x, x') (y, y') = (crop x y, crop x' y')
62+
63+-- | Trivial queries have trivial results.
64+instance Query () where
65+ type QueryResult () = ()
66+ crop _ _ = ()
67+
68+-- | The result of an absurd query is trivial; If you can ask the question, the
69+-- answer cannot tell you anything you didn't already know.
70+--
71+-- 'QueryResult Void = @Void@' seems like it would also work, but that has
72+-- problems of robustness. In some applications, an unasked question can still
73+-- be answered, so it is important that the result is inhabited even when the
74+-- question isn't. Applications that wish to prevent this can mandate that the
75+-- query result be paired with the query: then the whole response will be
76+-- uninhabited as desired.
77+instance Query Void where
78+ type QueryResult Void = ()
79+ crop = absurd
80+
81+#if MIN_VERSION_base(4,12,0)
82+-- | We can lift queries into monoidal containers.
83+-- But beware of Applicatives whose monoid is different from (pure mempty, liftA2 mappend)
84+instance (Query q, Applicative f) => Query (Ap f q) where
85+ type QueryResult (Ap f q) = Ap f (QueryResult q)
86+ crop = liftA2 crop
87+#endif
88+
5389-- | QueryMorphism's must be group homomorphisms when acting on the query type
5490-- and compatible with the query relationship when acting on the query result.
5591data QueryMorphism q q' = QueryMorphism
@@ -89,7 +125,7 @@ instance Group SelectedCount where
89125
90126instance Additive SelectedCount
91127
92−-- | The Semigroup/Monoid/Group instances for a Query containing 'SelectedCount's should use
128+-- | The Semigroup\/Monoid\/Group instances for a Query containing 'SelectedCount's should use
93129-- this function which returns Nothing if the result is 0. This allows the pruning of leaves
94130-- of the 'Query' that are no longer wanted.
95131combineSelectedCounts :: SelectedCount -> SelectedCount -> Maybe SelectedCount
@@ -97,12 +133,12 @@ combineSelectedCounts (SelectedCount i) (SelectedCount j) = if i == negate j the
97133
98134-- | A class that allows sending of 'Query's and retrieval of 'QueryResult's. See 'queryDyn' for a commonly
99135-- used interface.
100−class (Group q, Additive q, Query q) => MonadQuery t q m | m -> q t where
136+class (Group q, Additive q, Query q, Monad m) => MonadQuery t q m | m -> q t where
101137 tellQueryIncremental :: Incremental t (AdditivePatch q) -> m ()
102138 askQueryResult :: m (Dynamic t (QueryResult q))
103139 queryIncremental :: Incremental t (AdditivePatch q) -> m (Dynamic t (QueryResult q))
104140
105−instance (Monad m, MonadQuery t q m) => MonadQuery t q (ReaderT r m) where
141+instance MonadQuery t q m => MonadQuery t q (ReaderT r m) where
106142 tellQueryIncremental = lift . tellQueryIncremental
107143 askQueryResult = lift askQueryResult
108144 queryIncremental = lift . queryIncremental
@@ -112,7 +148,11 @@ tellQueryDyn :: (Reflex t, MonadQuery t q m) => Dynamic t q -> m ()
112148tellQueryDyn d = tellQueryIncremental $ unsafeBuildIncremental (sample (current d)) $ attachWith (\old new -> AdditivePatch $ new ~~ old) (current d) (updated d)
113149
114150-- | Retrieve 'Dynamic'ally updating 'QueryResult's for a 'Dynamic'ally updating 'Query'.
115−queryDyn :: (Reflex t, Monad m, MonadQuery t q m) => Dynamic t q -> m (Dynamic t (QueryResult q))
151+queryDyn :: (Reflex t, MonadQuery t q m) => Dynamic t q -> m (Dynamic t (QueryResult q))
116152queryDyn q = do
117153 tellQueryDyn q
118154 zipDynWith crop q <$> askQueryResult
155+
156+-- | Use a query morphism to operate on a smaller version of a query.
157+subQuery :: (Reflex t, MonadQuery t q2 m) => QueryMorphism q1 q2 -> Dynamic t q1 -> m (Dynamic t (QueryResult q1))
158+subQuery (QueryMorphism f g) x = fmap g <$> queryDyn (fmap f x)
src/Reflex/Spider/Internal.hs+419−256
@@ -38,7 +38,6 @@ import Control.Monad hiding (forM, forM_, mapM, mapM_)
3838import Control.Monad.Exception
3939import Control.Monad.Identity hiding (forM, forM_, mapM, mapM_)
4040import Control.Monad.Primitive
41−--import Control.Monad.Reader hiding (forM, forM_, mapM, mapM_)
4241import Control.Monad.Reader.Class
4342import Control.Monad.IO.Class
4443import Control.Monad.ReaderIO
@@ -61,13 +60,13 @@ import qualified Data.IntMap.Strict as IntMap
6160import Data.IORef
6261import Data.Kind (Type)
6362import Data.Maybe hiding (mapMaybe)
64−import Data.Monoid ((<>))
63+import Data.Monoid (mempty, (<>))
6564import Data.Proxy
6665import Data.These
6766import Data.Traversable
6867import Data.Type.Equality ((:~:)(Refl))
6968import Data.Witherable (Filterable, mapMaybe)
70−import GHC.Exts
69+import GHC.Exts hiding (toList)
7170import GHC.IORef (IORef (..))
7271import GHC.Stack
7372import Reflex.FastWeak
@@ -86,14 +85,16 @@ import Data.Zip (Zip (..))
8685
8786#ifdef DEBUG_CYCLES
8887import Control.Monad.State hiding (forM, forM_, mapM, mapM_, sequence)
88+#endif
89+
8990import Data.List.NonEmpty (NonEmpty (..), nonEmpty)
9091import qualified Data.List.NonEmpty as NonEmpty
91−import Data.Monoid (mempty)
9292import Data.Tree (Forest, Tree (..), drawForest)
93−#endif
93+import Data.List (isPrefixOf)
9494
95−import Data.FastWeakBag (FastWeakBag)
95+import Data.FastWeakBag (FastWeakBag, FastWeakBagTicket)
9696import qualified Data.FastWeakBag as FastWeakBag
97+
9798import Data.Reflection
9899import Data.Some (Some(Some))
99100import Data.Type.Coercion
@@ -104,21 +105,26 @@ import qualified Reflex.Class
104105import qualified Reflex.Class as R
105106import qualified Reflex.Host.Class
106107import Reflex.NotReady.Class
107−import Reflex.Patch
108−import qualified Reflex.Patch.DMapWithMove as PatchDMapWithMove
108+import Data.Patch
109+import qualified Data.Patch.DMapWithMove as PatchDMapWithMove
109110import Reflex.PerformEvent.Base (PerformEventT)
110111
111112#ifdef DEBUG_TRACE_EVENTS
112113import qualified Data.ByteString.Char8 as BS8
113114import System.IO (stderr)
115+import Data.List (isPrefixOf)
114116#endif
115117
116−#ifdef DEBUG_TRACE_EVENTS
118+-- TODO stdout might not be the best channel for debug output
119+debugStrLn :: String -> IO ()
120+debugStrLn = putStrLn
117121
122+#ifdef DEBUG_TRACE_EVENTS
118123withStackOneLine :: (BS8.ByteString -> a) -> a
119124withStackOneLine expr = unsafePerformIO $ do
120125 stack <- currentCallStack
121− return (expr (BS8.pack (unwords (reverse stack))))
126+ return (expr . BS8.pack . unwords . dropInternal . reverse $ stack)
127+ where dropInternal = filterStack "Reflex.Spider.Internal"
122128
123129#endif
124130
@@ -129,28 +135,42 @@ debugInvalidateHeight :: Bool
129135debugInvalidate :: Bool
130136
131137#ifdef DEBUG
132−
133138#define DEBUG_NODEIDS
134139
140+#ifdef DEBUG_TRACE_PROPAGATION
135141debugPropagate = True
142+#else
143+debugPropagate = False
144+#endif
136145
146+#ifdef DEBUG_TRACE_HEIGHT
147+debugInvalidateHeight = True
148+#else
137149debugInvalidateHeight = False
150+#endif
138151
152+#ifdef DEBUG_TRACE_INVALIDATION
139153debugInvalidate = True
154+#else
155+debugInvalidate = False
156+#endif
140157
141158class HasNodeId a where
142159 getNodeId :: a -> Int
143160
144−instance HasNodeId (Hold x p a) where
145− getNodeId = holdNodeId
161+instance HasNodeId (CacheSubscribed x a) where
162+ getNodeId = _cacheSubscribed_nodeId
163+
164+instance HasNodeId (FanInt x a) where
165+ getNodeId = _fanInt_nodeId
146166
147−instance HasNodeId (PushSubscribed x a b) where
148− getNodeId = pushSubscribedNodeId
167+instance HasNodeId (Hold x p) where
168+ getNodeId = holdNodeId
149169
150170instance HasNodeId (SwitchSubscribed x a) where
151171 getNodeId = switchSubscribedNodeId
152172
153−instance HasNodeId (FanSubscribed x a) where
173+instance HasNodeId (FanSubscribed x v a) where
154174 getNodeId = fanSubscribedNodeId
155175
156176instance HasNodeId (CoincidenceSubscribed x a) where
@@ -164,41 +184,16 @@ instance HasNodeId (Pull x a) where
164184
165185{-# INLINE showNodeId #-}
166186showNodeId :: HasNodeId a => a -> String
167−showNodeId = ("#"<>) . show . getNodeId
168−
169−showSubscriberType :: Subscriber x a -> String
170−showSubscriberType = \case
171− SubscriberPush _ _ -> "SubscriberPush"
172− SubscriberMerge _ _ -> "SubscriberMerge"
173− SubscriberFan _ -> "SubscriberFan"
174− SubscriberMergeChange _ -> "SubscriberMergeChange"
175− SubscriberHold _ -> "SubscriberHold"
176− SubscriberHoldIdentity _ -> "SubscriberHoldIdentity"
177− SubscriberSwitch _ -> "SubscriberSwitch"
178− SubscriberCoincidenceOuter _ -> "SubscriberCoincidenceOuter"
179− SubscriberCoincidenceInner _ -> "SubscriberCoincidenceInner"
180− SubscriberHandle -> "SubscriberHandle"
181−
182−showEventType :: Event x a -> String
183−showEventType = \case
184− EventRoot _ _ -> "EventRoot"
185− EventNever -> "EventNever"
186− EventPush _ -> "EventPush"
187− EventMerge _ -> "EventMerge"
188− EventFan _ _ -> "EventFan"
189− EventSwitch _ -> "EventSwitch"
190− EventCoincidence _ -> "EventCoincidence"
191− EventHold _ -> "EventHold"
192− EventDyn _ -> "EventDyn"
193− EventHoldIdentity _ -> "EventHoldIdentity"
194− EventDynIdentity _ -> "EventDynIdentity"
187+showNodeId = showNodeId' . getNodeId
188+
189+showNodeId' :: Int -> String
190+showNodeId' = ("#"<>) . show
191+
195192
196193#else
197194
198195debugPropagate = False
199−
200196debugInvalidateHeight = False
201−
202197debugInvalidate = False
203198
204199-- This must be inline, or error messages will cause memory leaks due to retaining the node in question
@@ -206,6 +201,10 @@ debugInvalidate = False
206201showNodeId :: a -> String
207202showNodeId _ = ""
208203
204+{-# INLINE showNodeId' #-}
205+showNodeId' :: Int -> String
206+showNodeId' _ = ""
207+
209208#endif
210209
211210#ifdef DEBUG_NODEIDS
@@ -251,9 +250,9 @@ subscribeAndRead = unEvent
251250-- caching; if the computation function is very cheap, this is (much) more
252251-- efficient than 'push'
253252{-# INLINE [1] pushCheap #-}
254−pushCheap :: (a -> ComputeM x (Maybe b)) -> Event x a -> Event x b
253+pushCheap :: HasSpiderTimeline x => (a -> ComputeM x (Maybe b)) -> Event x a -> Event x b
255254pushCheap !f e = Event $ \sub -> do
256− (subscription, occ) <- subscribeAndRead e $ sub
255+ (subscription, occ) <- subscribeAndRead e $ debugSubscriber' "push" $ sub
257256 { subscriberPropagate = \a -> do
258257 mb <- f a
259258 mapM_ (subscriberPropagate sub) mb
@@ -272,10 +271,10 @@ terminalSubscriber p = Subscriber
272271
273272-- | Subscribe to an Event only for the duration of one occurrence
274273{-# INLINE subscribeAndReadHead #-}
275−subscribeAndReadHead :: Event x a -> Subscriber x a -> EventM x (EventSubscription x, Maybe a)
274+subscribeAndReadHead :: HasSpiderTimeline x => Event x a -> Subscriber x a -> EventM x (EventSubscription x, Maybe a)
276275subscribeAndReadHead e sub = do
277276 subscriptionRef <- liftIO $ newIORef $ error "subscribeAndReadHead: not initialized"
278− (subscription, occ) <- subscribeAndRead e $ sub
277+ (subscription, occ) <- subscribeAndRead e $ debugSubscriber' "head" $ sub
279278 { subscriberPropagate = \a -> do
280279 liftIO $ unsubscribe =<< readIORef subscriptionRef
281280 subscriberPropagate sub a
@@ -286,24 +285,29 @@ subscribeAndReadHead e sub = do
286285 return (subscription, occ)
287286
288287--TODO: Make this lazy in its input event
289−headE :: (MonadIO m, Defer (SomeMergeInit x) m) => Event x a -> m (Event x a)
288+headE :: (MonadIO m, Defer (SomeMergeInit x) m, HasSpiderTimeline x) => Event x a -> m (Event x a)
290289headE originalE = do
291290 parent <- liftIO $ newIORef $ Just originalE
292291 defer $ SomeMergeInit $ do --TODO: Rename SomeMergeInit appropriately
293292 let clearParent = liftIO $ writeIORef parent Nothing
294− (_, occ) <- subscribeAndReadHead originalE $ terminalSubscriber $ \_ -> clearParent
293+ (_, occ) <- subscribeAndReadHead originalE $ terminalSubscriber $ const clearParent
295294 when (isJust occ) clearParent
296− return $ Event $ \sub -> do
295+ return $ Event $ \sub ->
297296 liftIO (readIORef parent) >>= \case
298− Nothing -> return (EventSubscription (return ()) $ EventSubscribed zeroRef $ toAny (), Nothing)
297+ Nothing -> subscribeAndReadNever
299298 Just e -> subscribeAndReadHead e sub
300299
301300data CacheSubscribed x a
302301 = CacheSubscribed { _cacheSubscribed_subscribers :: {-# UNPACK #-} !(FastWeakBag (Subscriber x a))
303302 , _cacheSubscribed_parent :: {-# UNPACK #-} !(EventSubscription x)
304303 , _cacheSubscribed_occurrence :: {-# UNPACK #-} !(IORef (Maybe a))
304+#ifdef DEBUG_NODEIDS
305+ , _cacheSubscribed_nodeId :: {-# UNPACK #-} !Int
306+#endif
305307 }
306308
309+
310+
307311-- | Construct an 'Event' whose value is guaranteed not to be recomputed
308312-- repeatedly
309313--
@@ -328,18 +332,23 @@ cacheEvent e =
328332 subscribedTicket <- liftIO (readIORef mSubscribedRef >>= getFastWeakTicket) >>= \case
329333 Just subscribedTicket -> return subscribedTicket
330334 Nothing -> do
335+#ifdef DEBUG_NODEIDS
336+ nodeId <- liftIO newNodeId
337+#endif
331338 subscribers <- liftIO FastWeakBag.empty
332339 occRef <- liftIO $ newIORef Nothing -- This should never be read prior to being set below
340+#ifdef DEBUG_NODEIDS
341+ (parentSub, occ) <- subscribeAndRead e $ debugSubscriber' ("cacheEvent" <> showNodeId' nodeId) $ Subscriber
342+#else
333343 (parentSub, occ) <- subscribeAndRead e $ Subscriber
334− { subscriberPropagate = \a -> do
335− liftIO $ writeIORef occRef $ Just a
336− scheduleClear occRef
337− propagateFast a subscribers
338− , subscriberInvalidateHeight = \old -> do
339− FastWeakBag.traverse_ subscribers $ invalidateSubscriberHeight old
340− , subscriberRecalculateHeight = \new -> do
341− FastWeakBag.traverse_ subscribers $ recalculateSubscriberHeight new
342− }
344+#endif
345+ { subscriberPropagate = \a -> do
346+ liftIO $ writeIORef occRef (Just a)
347+ scheduleClear occRef
348+ propagateFast a subscribers
349+ , subscriberInvalidateHeight = FastWeakBag.traverse_ subscribers . invalidateSubscriberHeight
350+ , subscriberRecalculateHeight = FastWeakBag.traverse_ subscribers . recalculateSubscriberHeight
351+ }
343352 when (isJust occ) $ do
344353 liftIO $ writeIORef occRef occ -- Set the initial value of occRef; we don't need to do this if occ is Nothing
345354 scheduleClear occRef
@@ -347,29 +356,45 @@ cacheEvent e =
347356 { _cacheSubscribed_subscribers = subscribers
348357 , _cacheSubscribed_parent = parentSub
349358 , _cacheSubscribed_occurrence = occRef
359+#ifdef DEBUG_NODEIDS
360+ , _cacheSubscribed_nodeId = nodeId
361+#endif
350362 }
351363 subscribedTicket <- liftIO $ mkFastWeakTicket subscribed
352364 liftIO $ writeIORef mSubscribedRef =<< getFastWeakTicketWeak subscribedTicket
353365 return subscribedTicket
354− liftIO $ do
355− subscribed <- getFastWeakTicketValue subscribedTicket
356− ticket <- FastWeakBag.insert sub $ _cacheSubscribed_subscribers subscribed
357− occ <- readIORef $ _cacheSubscribed_occurrence subscribed
358− let es = EventSubscription
359− { _eventSubscription_unsubscribe = do
360− FastWeakBag.remove ticket
361− isEmpty <- FastWeakBag.isEmpty $ _cacheSubscribed_subscribers subscribed
362− when isEmpty $ do
363− writeIORef mSubscribedRef emptyFastWeak
364− unsubscribe $ _cacheSubscribed_parent subscribed
365− touch ticket
366− touch subscribedTicket
367− , _eventSubscription_subscribed = EventSubscribed
368− { eventSubscribedHeightRef = eventSubscribedHeightRef $ _eventSubscription_subscribed $ _cacheSubscribed_parent subscribed
369− , eventSubscribedRetained = toAny subscribedTicket
370− }
371− }
372− return (es, occ)
366+ liftIO $ cacheSubscription sub mSubscribedRef subscribedTicket
367+
368+cacheSubscription :: Subscriber x a -> IORef (FastWeak (CacheSubscribed x a))
369+ -> FastWeakTicket (CacheSubscribed x a) -> IO (EventSubscription x, Maybe a)
370+cacheSubscription sub mSubscribedRef subscribedTicket = do
371+ subscribed <- getFastWeakTicketValue subscribedTicket
372+ ticket <- FastWeakBag.insert sub $ _cacheSubscribed_subscribers subscribed
373+ occ <- readIORef $ _cacheSubscribed_occurrence subscribed
374+
375+ let parentSub = _cacheSubscribed_parent subscribed
376+ es = EventSubscription
377+ { _eventSubscription_unsubscribe = do
378+ FastWeakBag.remove ticket
379+
380+ isEmpty <- FastWeakBag.isEmpty $ _cacheSubscribed_subscribers subscribed
381+ when isEmpty $ do
382+ writeIORef mSubscribedRef emptyFastWeak
383+ unsubscribe parentSub
384+ touch ticket
385+ touch subscribedTicket
386+ , _eventSubscription_subscribed = EventSubscribed
387+ { eventSubscribedHeightRef = eventSubscribedHeightRef $ _eventSubscription_subscribed parentSub
388+ , eventSubscribedRetained = toAny subscribedTicket
389+#ifdef DEBUG_CYCLES
390+ , eventSubscribedGetParents = return [_eventSubscription_subscribed parentSub]
391+ , eventSubscribedHasOwnHeightRef = False
392+ , eventSubscribedWhoCreated = whoCreatedIORef mSubscribedRef
393+#endif
394+ }
395+ }
396+ return (es, occ)
397+
373398
374399subscribe :: Event x a -> Subscriber x a -> EventM x (EventSubscription x)
375400subscribe e s = fst <$> subscribeAndRead e s
@@ -381,11 +406,14 @@ wrap tag getSpecificSubscribed sub = do
381406 let es = tag subd
382407 return (EventSubscription (WeakBag.remove sln >> touch sln) es, occ)
383408
384−eventRoot :: GCompare k => k a -> Root x k -> Event x a
409+eventRoot :: (GCompare k, HasSpiderTimeline x) => k a -> Root x k -> Event x a
385410eventRoot !k !r = Event $ wrap eventSubscribedRoot $ liftIO . getRootSubscribed k r
386411
412+subscribeAndReadNever :: EventM x (EventSubscription x, Maybe a)
413+subscribeAndReadNever = return (EventSubscription (return ()) eventSubscribedNever, Nothing)
414+
387415eventNever :: Event x a
388−eventNever = Event $ \_ -> return (EventSubscription (return ()) eventSubscribedNever, Nothing)
416+eventNever = Event $ const subscribeAndReadNever
389417
390418eventFan :: (GCompare k, HasSpiderTimeline x) => k a -> Fan x k v -> Event x (v a)
391419eventFan !k !f = Event $ wrap eventSubscribedFan $ getFanSubscribed k f
@@ -422,16 +450,6 @@ data Subscriber x a = Subscriber
422450 , subscriberRecalculateHeight :: !(Height -> IO ())
423451 }
424452
425−--TODO: Move this comment to WeakBag
426−-- These function are constructor functions that are marked NOINLINE so they are
427−-- opaque to GHC. If we do not do this, then GHC will sometimes fuse the constructor away
428−-- so any weak references that are attached to the constructors will have their
429−-- finalizer run. Using the opaque constructor, does not see the
430−-- constructor application, so it behaves like an IORef and cannot be fused away.
431−--
432−-- The result is also evaluated to WHNF, since forcing a thunk invalidates
433−-- the weak pointer to it in some cases.
434−
435453newSubscriberHold :: (HasSpiderTimeline x, Patch p) => Hold x p -> IO (Subscriber x p)
436454newSubscriberHold h = return $ Subscriber
437455 { subscriberPropagate = {-# SCC "traverseHold" #-} propagateSubscriberHold h
@@ -440,10 +458,10 @@ newSubscriberHold h = return $ Subscriber
440458 }
441459
442460newSubscriberFan :: forall x k v. (HasSpiderTimeline x, GCompare k) => FanSubscribed x k v -> IO (Subscriber x (DMap k v))
443−newSubscriberFan subscribed = return $ Subscriber
461+newSubscriberFan subscribed = debugSubscriber ("SubscriberFan " <> showNodeId subscribed) $ Subscriber
444462 { subscriberPropagate = \a -> {-# SCC "traverseFan" #-} do
445463 subs <- liftIO $ readIORef $ fanSubscribedSubscribers subscribed
446− tracePropagate (Proxy :: Proxy x) $ "SubscriberFan" <> showNodeId subscribed <> ": " ++ show (DMap.size subs) ++ " keys subscribed, " ++ show (DMap.size a) ++ " keys firing"
464+ tracePropagate (Proxy :: Proxy x) $ show (DMap.size subs) <> " keys subscribed, " <> show (DMap.size a) <> " keys firing"
447465 liftIO $ writeIORef (fanSubscribedOccurrence subscribed) $ Just a
448466 scheduleClear $ fanSubscribedOccurrence subscribed
449467 let f _ (Pair v subsubs) = do
@@ -452,50 +470,40 @@ newSubscriberFan subscribed = return $ Subscriber
452470 _ <- DMap.traverseWithKey f $ DMap.intersectionWithKey (\_ -> Pair) a subs --TODO: Would be nice to have DMap.traverse_
453471 return ()
454472 , subscriberInvalidateHeight = \old -> do
455− when debugInvalidateHeight $ putStrLn $ "invalidateSubscriberHeight: SubscriberFan" <> showNodeId subscribed
456473 subscribers <- readIORef $ fanSubscribedSubscribers subscribed
457474 forM_ (DMap.toList subscribers) $ \(_ :=> v) -> WeakBag.traverse_ (_fanSubscribedChildren_list v) $ invalidateSubscriberHeight old
458− when debugInvalidateHeight $ putStrLn $ "invalidateSubscriberHeight: SubscriberFan" <> showNodeId subscribed <> " done"
459475 , subscriberRecalculateHeight = \new -> do
460− when debugInvalidateHeight $ putStrLn $ "recalculateSubscriberHeight: SubscriberFan" <> showNodeId subscribed
461476 subscribers <- readIORef $ fanSubscribedSubscribers subscribed
462477 forM_ (DMap.toList subscribers) $ \(_ :=> v) -> WeakBag.traverse_ (_fanSubscribedChildren_list v) $ recalculateSubscriberHeight new
463− when debugInvalidateHeight $ putStrLn $ "recalculateSubscriberHeight: SubscriberFan" <> showNodeId subscribed <> " done"
464478 }
465479
466480newSubscriberSwitch :: forall x a. HasSpiderTimeline x => SwitchSubscribed x a -> IO (Subscriber x a)
467−newSubscriberSwitch subscribed = return $ Subscriber
481+newSubscriberSwitch subscribed = debugSubscriber ("SubscriberCoincidenceOuter" <> showNodeId subscribed) $ Subscriber
468482 { subscriberPropagate = \a -> {-# SCC "traverseSwitch" #-} do
469− tracePropagate (Proxy :: Proxy x) $ "SubscriberSwitch" <> showNodeId subscribed
470483 liftIO $ writeIORef (switchSubscribedOccurrence subscribed) $ Just a
471484 scheduleClear $ switchSubscribedOccurrence subscribed
472485 propagate a $ switchSubscribedSubscribers subscribed
473486 , subscriberInvalidateHeight = \_ -> do
474− when debugInvalidateHeight $ putStrLn $ "invalidateSubscriberHeight: SubscriberSwitch" <> showNodeId subscribed
475487 oldHeight <- readIORef $ switchSubscribedHeight subscribed
476488 when (oldHeight /= invalidHeight) $ do
477489 writeIORef (switchSubscribedHeight subscribed) $! invalidHeight
478490 WeakBag.traverse_ (switchSubscribedSubscribers subscribed) $ invalidateSubscriberHeight oldHeight
479− when debugInvalidateHeight $ putStrLn $ "invalidateSubscriberHeight: SubscriberSwitch" <> showNodeId subscribed <> " done"
480− , subscriberRecalculateHeight = \new -> do
481− when debugInvalidateHeight $ putStrLn $ "recalculateSubscriberHeight: SubscriberSwitch" <> showNodeId subscribed
482− updateSwitchHeight new subscribed
483− when debugInvalidateHeight $ putStrLn $ "recalculateSubscriberHeight: SubscriberSwitch" <> showNodeId subscribed <> " done"
484− }
491+ , subscriberRecalculateHeight = (`updateSwitchHeight` subscribed)
492+ }
485493
486494newSubscriberCoincidenceOuter :: forall x b. HasSpiderTimeline x => CoincidenceSubscribed x b -> IO (Subscriber x (Event x b))
487−newSubscriberCoincidenceOuter subscribed = return $ Subscriber
495+newSubscriberCoincidenceOuter subscribed = debugSubscriber ("SubscriberCoincidenceOuter" <> showNodeId subscribed) $ Subscriber
488496 { subscriberPropagate = \a -> {-# SCC "traverseCoincidenceOuter" #-} do
489− tracePropagate (Proxy :: Proxy x) $ "SubscriberCoincidenceOuter" <> showNodeId subscribed
490497 outerHeight <- liftIO $ readIORef $ coincidenceSubscribedHeight subscribed
491498 tracePropagate (Proxy :: Proxy x) $ " outerHeight = " <> show outerHeight
492499 (occ, innerHeight, innerSubd) <- subscribeCoincidenceInner a outerHeight subscribed
493500 tracePropagate (Proxy :: Proxy x) $ " isJust occ = " <> show (isJust occ)
494501 tracePropagate (Proxy :: Proxy x) $ " innerHeight = " <> show innerHeight
502+
495503 liftIO $ writeIORef (coincidenceSubscribedInnerParent subscribed) $ Just innerSubd
496504 scheduleClear $ coincidenceSubscribedInnerParent subscribed
497505 case occ of
498− Nothing -> do
506+ Nothing ->
499507 when (innerHeight > outerHeight) $ liftIO $ do -- If the event fires, it will fire at a later height
500508 writeIORef (coincidenceSubscribedHeight subscribed) $! innerHeight
501509 WeakBag.traverse_ (coincidenceSubscribedSubscribers subscribed) $ invalidateSubscriberHeight outerHeight
@@ -504,20 +512,13 @@ newSubscriberCoincidenceOuter subscribed = return $ Subscriber
504512 liftIO $ writeIORef (coincidenceSubscribedOccurrence subscribed) occ
505513 scheduleClear $ coincidenceSubscribedOccurrence subscribed
506514 propagate o $ coincidenceSubscribedSubscribers subscribed
507− , subscriberInvalidateHeight = \_ -> do
508− when debugInvalidateHeight $ putStrLn $ "invalidateSubscriberHeight: SubscriberCoincidenceOuter" <> showNodeId subscribed
509− invalidateCoincidenceHeight subscribed
510− when debugInvalidateHeight $ putStrLn $ "invalidateSubscriberHeight: SubscriberCoincidenceOuter" <> showNodeId subscribed <> " done"
511− , subscriberRecalculateHeight = \_ -> do
512− when debugInvalidateHeight $ putStrLn $ "recalculateSubscriberHeight: SubscriberCoincidenceOuter" <> showNodeId subscribed
513− recalculateCoincidenceHeight subscribed
514− when debugInvalidateHeight $ putStrLn $ "recalculateSubscriberHeight: SubscriberCoincidenceOuter" <> showNodeId subscribed <> " done"
515+ , subscriberInvalidateHeight = \_ -> invalidateCoincidenceHeight subscribed
516+ , subscriberRecalculateHeight = \_ -> recalculateCoincidenceHeight subscribed
515517 }
516518
517519newSubscriberCoincidenceInner :: forall x a. HasSpiderTimeline x => CoincidenceSubscribed x a -> IO (Subscriber x a)
518−newSubscriberCoincidenceInner subscribed = return $ Subscriber
520+newSubscriberCoincidenceInner subscribed = debugSubscriber ("SubscriberCoincidenceInner" <> showNodeId subscribed) $ Subscriber
519521 { subscriberPropagate = \a -> {-# SCC "traverseCoincidenceInner" #-} do
520− tracePropagate (Proxy :: Proxy x) $ "SubscriberCoincidenceInner" <> showNodeId subscribed
521522 occ <- liftIO $ readIORef $ coincidenceSubscribedOccurrence subscribed
522523 case occ of
523524 Just _ -> return () -- SubscriberCoincidenceOuter must have already propagated this event
@@ -525,14 +526,8 @@ newSubscriberCoincidenceInner subscribed = return $ Subscriber
525526 liftIO $ writeIORef (coincidenceSubscribedOccurrence subscribed) $ Just a
526527 scheduleClear $ coincidenceSubscribedOccurrence subscribed
527528 propagate a $ coincidenceSubscribedSubscribers subscribed
528− , subscriberInvalidateHeight = \_ -> do
529− when debugInvalidateHeight $ putStrLn $ "invalidateSubscriberHeight: SubscriberCoincidenceInner" <> showNodeId subscribed
530− invalidateCoincidenceHeight subscribed
531− when debugInvalidateHeight $ putStrLn $ "invalidateSubscriberHeight: SubscriberCoincidenceInner" <> showNodeId subscribed <> " done"
532− , subscriberRecalculateHeight = \_ -> do
533− when debugInvalidateHeight $ putStrLn $ "recalculateSubscriberHeight: SubscriberCoincidenceInner" <> showNodeId subscribed
534− recalculateCoincidenceHeight subscribed
535− when debugInvalidateHeight $ putStrLn $ "recalculateSubscriberHeight: SubscriberCoincidenceInner" <> showNodeId subscribed <> " done"
529+ , subscriberInvalidateHeight = \_ -> invalidateCoincidenceHeight subscribed
530+ , subscriberRecalculateHeight = \_ -> recalculateCoincidenceHeight subscribed
536531 }
537532
538533invalidateSubscriberHeight :: Height -> Subscriber x a -> IO ()
@@ -542,15 +537,15 @@ recalculateSubscriberHeight :: Height -> Subscriber x a -> IO ()
542537recalculateSubscriberHeight = flip subscriberRecalculateHeight
543538
544539-- | Propagate everything at the current height
545−propagate :: a -> WeakBag (Subscriber x a) -> EventM x ()
546−propagate a subscribers = withIncreasedDepth $ do
540+propagate :: forall x a. HasSpiderTimeline x => a -> WeakBag (Subscriber x a) -> EventM x ()
541+propagate a subscribers = withIncreasedDepth (Proxy::Proxy x) $
547542 -- Note: in the following traversal, we do not visit nodes that are added to the list during our traversal; they are new events, which will necessarily have full information already, so there is no need to traverse them
548543 --TODO: Should we check if nodes already have their values before propagating? Maybe we're re-doing work
549544 WeakBag.traverse_ subscribers $ \s -> subscriberPropagate s a
550545
551546-- | Propagate everything at the current height
552−propagateFast :: a -> FastWeakBag (Subscriber x a) -> EventM x ()
553−propagateFast a subscribers = withIncreasedDepth $ do
547+propagateFast :: forall x a. HasSpiderTimeline x => a -> FastWeakBag (Subscriber x a) -> EventM x ()
548+propagateFast a subscribers = withIncreasedDepth (Proxy::Proxy x) $
554549 -- Note: in the following traversal, we do not visit nodes that are added to the list during our traversal; they are new events, which will necessarily have full information already, so there is no need to traverse them
555550 --TODO: Should we check if nodes already have their values before propagating? Maybe we're re-doing work
556551 FastWeakBag.traverse_ subscribers $ \s -> subscriberPropagate s a
@@ -571,7 +566,7 @@ data EventSubscribed x = EventSubscribed
571566 { eventSubscribedHeightRef :: {-# UNPACK #-} !(IORef Height)
572567 , eventSubscribedRetained :: {-# NOUNPACK #-} !Any
573568#ifdef DEBUG_CYCLES
574− , eventSubscribedGetParents :: !(IO [Some (EventSubscribed x)]) -- For debugging loops
569+ , eventSubscribedGetParents :: !(IO [EventSubscribed x]) -- For debugging loops
575570 , eventSubscribedHasOwnHeightRef :: !Bool
576571 , eventSubscribedWhoCreated :: !(IO [String])
577572#endif
@@ -604,7 +599,7 @@ eventSubscribedFan !subscribed = EventSubscribed
604599 { eventSubscribedHeightRef = eventSubscribedHeightRef $ _eventSubscription_subscribed $ fanSubscribedParent subscribed
605600 , eventSubscribedRetained = toAny subscribed
606601#ifdef DEBUG_CYCLES
607− , eventSubscribedGetParents = return [Some $ _eventSubscription_subscribed $ fanSubscribedParent subscribed]
602+ , eventSubscribedGetParents = return [_eventSubscription_subscribed $ fanSubscribedParent subscribed]
608603 , eventSubscribedHasOwnHeightRef = False
609604 , eventSubscribedWhoCreated = whoCreatedIORef $ fanSubscribedCachedSubscribed subscribed
610605#endif
@@ -617,7 +612,7 @@ eventSubscribedSwitch !subscribed = EventSubscribed
617612#ifdef DEBUG_CYCLES
618613 , eventSubscribedGetParents = do
619614 s <- readIORef $ switchSubscribedCurrentParent subscribed
620− return [Some $ _eventSubscription_subscribed s]
615+ return [_eventSubscription_subscribed s]
621616 , eventSubscribedHasOwnHeightRef = True
622617 , eventSubscribedWhoCreated = whoCreatedIORef $ switchSubscribedCachedSubscribed subscribed
623618#endif
@@ -630,8 +625,8 @@ eventSubscribedCoincidence !subscribed = EventSubscribed
630625#ifdef DEBUG_CYCLES
631626 , eventSubscribedGetParents = do
632627 innerSubscription <- readIORef $ coincidenceSubscribedInnerParent subscribed
633− let outerParent = Some $ _eventSubscription_subscribed $ coincidenceSubscribedOuterParent subscribed
634− innerParents = maybeToList $ fmap Some innerSubscription
628+ let outerParent = _eventSubscription_subscribed $ coincidenceSubscribedOuterParent subscribed
629+ innerParents = maybeToList $ innerSubscription
635630 return $ outerParent : innerParents
636631 , eventSubscribedHasOwnHeightRef = True
637632 , eventSubscribedWhoCreated = whoCreatedIORef $ coincidenceSubscribedCachedSubscribed subscribed
@@ -645,15 +640,15 @@ getEventSubscribedHeight es = readIORef $ eventSubscribedHeightRef es
645640whoCreatedEventSubscribed :: EventSubscribed x -> IO [String]
646641whoCreatedEventSubscribed = eventSubscribedWhoCreated
647642
648−walkInvalidHeightParents :: EventSubscribed x -> IO [Some (EventSubscribed x)]
643+walkInvalidHeightParents :: EventSubscribed x -> IO [EventSubscribed x]
649644walkInvalidHeightParents s0 = do
650− subscribers <- flip execStateT mempty $ ($ Some s0) $ fix $ \loop (Some s) -> do
645+ subscribers <- flip execStateT mempty $ ($ s0) $ fix $ \loop s -> do
651646 h <- liftIO $ readIORef $ eventSubscribedHeightRef s
652647 when (h == invalidHeight) $ do
653648 when (eventSubscribedHasOwnHeightRef s) $ liftIO $ writeIORef (eventSubscribedHeightRef s) $! invalidHeightBeingTraversed
654− modify (Some s :)
649+ modify (s :)
655650 mapM_ loop =<< liftIO (eventSubscribedGetParents s)
656− forM_ subscribers $ \(Some s) -> writeIORef (eventSubscribedHeightRef s) $! invalidHeight
651+ forM_ subscribers $ \s -> writeIORef (eventSubscribedHeightRef s) $! invalidHeight
657652 return subscribers
658653#endif
659654
@@ -1038,7 +1033,7 @@ data SomeMergeUpdate x = SomeMergeUpdate
10381033newtype SomeMergeInit x = SomeMergeInit { unSomeMergeInit :: EventM x () }
10391034
10401035-- EventM can do everything BehaviorM can, plus create holds
1041−newtype EventM x a = EventM { unEventM :: IO a } deriving (Functor, Applicative, Monad, MonadIO, MonadFix, MonadException, MonadAsyncException) -- The environment should be Nothing if we are not in a frame, and Just if we are - in which case it is a list of assignments to be done after the frame is over
1036+newtype EventM x a = EventM { unEventM :: IO a } deriving (Functor, Applicative, Monad, MonadIO, MonadFix, MonadException, MonadAsyncException)
10421037
10431038newtype MergeSubscribedParent x a = MergeSubscribedParent { unMergeSubscribedParent :: EventSubscription x }
10441039
@@ -1063,7 +1058,8 @@ heightBagFromList :: [Height] -> HeightBag
10631058heightBagFromList heights = heightBagVerify $ foldl' (flip heightBagAdd) heightBagEmpty heights
10641059
10651060heightBagAdd :: Height -> HeightBag -> HeightBag
1066−heightBagAdd (Height h) (HeightBag s c) = heightBagVerify $ HeightBag (succ s) $ IntMap.insertWithKey (\_ _ old -> succ old) h 0 c
1061+heightBagAdd (Height h) (HeightBag s c) = heightBagVerify $ HeightBag (succ s) $
1062+ IntMap.insertWithKey (\_ _ old -> succ old) h 0 c
10671063
10681064heightBagRemove :: Height -> HeightBag -> HeightBag
10691065heightBagRemove (Height h) b@(HeightBag s c) = heightBagVerify $ case IntMap.lookup h c of
@@ -1072,6 +1068,13 @@ heightBagRemove (Height h) b@(HeightBag s c) = heightBagVerify $ case IntMap.loo
10721068 0 -> IntMap.delete h c
10731069 _ -> IntMap.insert h (pred old) c
10741070
1071+heightBagRemoveMaybe :: Height -> HeightBag -> Maybe HeightBag
1072+heightBagRemoveMaybe (Height h) b@(HeightBag s c) = heightBagVerify . removed <$> IntMap.lookup h c where
1073+ removed old = HeightBag (pred s) $ case old of
1074+ 0 -> IntMap.delete h c
1075+ _ -> IntMap.insert h (pred old) c
1076+
1077+
10751078heightBagMax :: HeightBag -> Height
10761079heightBagMax (HeightBag _ c) = case IntMap.maxViewWithKey c of
10771080 Just ((h, _), _) -> Height h
@@ -1319,55 +1322,110 @@ mkWeakPtrWithDebug x debugNote = do
13191322 x' <- evaluate x
13201323 mkWeakPtr x' $
13211324 if debugFinalize
1322− then Just $ putStrLn $ "finalizing: " ++ debugNote
1325+ then Just $ debugStrLn $ "finalizing: " ++ debugNote
13231326 else Nothing
13241327
13251328type WeakList a = [Weak a]
13261329
1327−{-# INLINE withIncreasedDepth #-}
1330+type CanTrace x m = (HasSpiderTimeline x, MonadIO m)
1331+
1332+
1333+
1334+
13281335#ifdef DEBUG
1329−withIncreasedDepth :: CanTrace x m => m a -> m a
1330−withIncreasedDepth a = do
1331− spiderTimeline <- askSpiderTimelineEnv
1332− liftIO $ modifyIORef' (_spiderTimeline_depth (unSTE spiderTimeline)) succ
1336+
1337+debugSubscriber :: forall x a. HasSpiderTimeline x => String -> Subscriber x a -> IO (Subscriber x a)
1338+debugSubscriber description = return . debugSubscriber' description
1339+
1340+debugSubscriber' :: forall x a. HasSpiderTimeline x => String -> Subscriber x a -> Subscriber x a
1341+debugSubscriber' description subscribed = Subscriber
1342+ {
1343+ subscriberPropagate = \m -> do
1344+ tracePropagate (Proxy :: Proxy x) ("subscriberPropagate: " <> description)
1345+ subscriberPropagate subscribed m
1346+ , subscriberInvalidateHeight = \old -> do
1347+ traceInvalidateHeight $ "invalidateSubscriberHeight: " <> description <> ", old = " <> show (unHeight old)
1348+ subscriberInvalidateHeight subscribed old
1349+ traceInvalidateHeight $ "invalidateSubscriberHeight: " <> description <> ", done"
1350+ , subscriberRecalculateHeight = \new -> do
1351+ traceInvalidateHeight $ "subscriberRecalculateHeight: " <> description <> ", new = " <> show (unHeight new)
1352+ subscriberRecalculateHeight subscribed new
1353+ traceInvalidateHeight $ "subscriberRecalculateHeight: " <> description <> ", done"
1354+ }
1355+
1356+
1357+{-# INLINE withIncreasedDepth #-}
1358+withIncreasedDepth :: forall proxy x m a. CanTrace x m => proxy x -> m a -> m a
1359+withIncreasedDepth _ a = do
1360+ liftIO $ modifyIORef' (_spiderTimeline_depth $ unSTE (spiderTimeline :: SpiderTimelineEnv x)) succ
13331361 result <- a
1334− liftIO $ modifyIORef' (_spiderTimeline_depth (unSTE spiderTimeline)) pred
1362+ liftIO $ modifyIORef' (_spiderTimeline_depth $ unSTE (spiderTimeline :: SpiderTimelineEnv x)) pred
13351363 return result
1336−#else
1337−withIncreasedDepth :: m a -> m a
1338−withIncreasedDepth = id
1339−#endif
1340−
1341−type CanTrace x m = (HasSpiderTimeline x, MonadIO m)
13421364
13431365{-# INLINE tracePropagate #-}
13441366tracePropagate :: (CanTrace x m) => proxy x -> String -> m ()
1345−tracePropagate p = traceWhen p debugPropagate
1367+tracePropagate p = when debugPropagate . trace p
13461368
13471369{-# INLINE traceInvalidate #-}
13481370traceInvalidate :: String -> IO ()
1349−traceInvalidate = when debugInvalidate . liftIO . putStrLn
1371+traceInvalidate = when debugInvalidate . liftIO . debugStrLn
1372+
1373+{-# INLINE traceInvalidateHeight #-}
1374+traceInvalidateHeight :: String -> IO ()
1375+traceInvalidateHeight = when debugInvalidateHeight . liftIO . debugStrLn
13501376
1351−{-# INLINE traceWhen #-}
1352−traceWhen :: (CanTrace x m) => proxy x -> Bool -> String -> m ()
1353−traceWhen p b message = traceMWhen p b $ return message
1377+{-# INLINE trace #-}
1378+trace :: (CanTrace x m) => proxy x -> String -> m ()
1379+trace p message = traceM p $ return message
13541380
1355−{-# INLINE traceMWhen #-}
1356−traceMWhen :: (CanTrace x m) => proxy x -> Bool -> m String -> m ()
1357−traceMWhen _ b getMessage = when b $ do
1381+{-# INLINE traceM #-}
1382+traceM :: forall x proxy m. (CanTrace x m) => proxy x -> m String -> m ()
1383+traceM _ getMessage = do
13581384 message <- getMessage
1359−#ifdef DEBUG
1360− spiderTimeline <- askSpiderTimelineEnv
1361− d <- liftIO $ readIORef $ _spiderTimeline_depth $ unSTE spiderTimeline
1385+ d <- liftIO $ readIORef $ _spiderTimeline_depth $ unSTE (spiderTimeline :: SpiderTimelineEnv x)
1386+ liftIO $ debugStrLn $ replicate d ' ' <> message
1387+
13621388#else
1363− let d = 0
1389+
1390+{-# INLINE withIncreasedDepth #-}
1391+withIncreasedDepth :: proxy x -> m a -> m a
1392+withIncreasedDepth _ = id
1393+
1394+{-# INLINE tracePropagate #-}
1395+tracePropagate :: (CanTrace x m) => proxy x -> String -> m ()
1396+tracePropagate _ _ = return ()
1397+
1398+{-# INLINE traceInvalidate #-}
1399+traceInvalidate :: String -> IO ()
1400+traceInvalidate _ = return ()
1401+
1402+{-# INLINE traceInvalidateHeight #-}
1403+traceInvalidateHeight :: String -> IO ()
1404+traceInvalidateHeight _ = return ()
1405+
1406+{-# INLINE debugSubscriber #-}
1407+debugSubscriber :: String -> Subscriber x a -> IO (Subscriber x a)
1408+debugSubscriber _ = return
1409+
1410+{-# INLINE debugSubscriber' #-}
1411+debugSubscriber' :: String -> Subscriber x a -> Subscriber x a
1412+debugSubscriber' _ = id
1413+
1414+
1415+
1416+{-# INLINE trace #-}
1417+trace :: (CanTrace x m) => proxy x -> String -> m ()
1418+trace _ _ = return ()
1419+
1420+{-# INLINE traceM #-}
1421+traceM :: (CanTrace x m) => proxy x -> m String -> m ()
1422+traceM _ _ = return ()
1423+
13641424#endif
1365− liftIO $ putStrLn $ replicate d ' ' <> message
13661425
13671426whoCreatedIORef :: IORef a -> IO [String]
13681427whoCreatedIORef (IORef a) = whoCreated $! a
13691428
1370−#ifdef DEBUG_CYCLES
13711429groupByHead :: Eq a => [NonEmpty a] -> [(a, NonEmpty [a])]
13721430groupByHead = \case
13731431 [] -> []
@@ -1378,20 +1436,54 @@ groupByHead = \case
13781436 | otherwise -> (x, xs :| []) : l
13791437
13801438listsToForest :: Eq a => [[a]] -> Forest a
1381−listsToForest l = fmap (\(a, l') -> Node a $ listsToForest $ toList l') $ groupByHead $ catMaybes $ fmap nonEmpty l
1439+listsToForest lists = buildForest <$> groupByHead (mapMaybe nonEmpty lists)
1440+ where buildForest (a, lists') = Node a $ listsToForest $ toList lists'
1441+
1442+showStacks :: [[String]] -> String
1443+showStacks = drawForest . listsToForest . fmap (filterStack "Reflex.Spider.Internal")
1444+
1445+filterStack :: String -> [String] -> [String]
1446+#ifdef DEBUG_HIDE_INTERNALS
1447+filterStack prefix = filter (not . (prefix `isPrefixOf`))
1448+#else
1449+filterStack prefix = id
1450+#endif
1451+
1452+#ifdef DEBUG_CYCLES
1453+
1454+data EventLoopException = EventLoopException [[String]]
1455+instance Exception EventLoopException
1456+
1457+instance Show EventLoopException where
1458+ show (EventLoopException stacks) = "causality loop detected:\n" <> if null stacks
1459+ then "no location information, compile with profiling enabled for stack tree"
1460+ else showStacks stacks
1461+
1462+#else
1463+
1464+data EventLoopException = EventLoopException
1465+instance Exception EventLoopException
1466+
1467+instance Show EventLoopException where
1468+ show EventLoopException = "causality loop detected: \n" <>
1469+ "compile reflex with flag 'debug-cycles' and compile with profiling enabled for stack tree"
1470+
13821471#endif
13831472
1473+
13841474{-# INLINE propagateSubscriberHold #-}
13851475propagateSubscriberHold :: forall x p. (HasSpiderTimeline x, Patch p) => Hold x p -> p -> EventM x ()
13861476propagateSubscriberHold h a = do
1387− {-# SCC "trace" #-} traceMWhen (Proxy :: Proxy x) debugPropagate $ liftIO $ do
1477+ {-# SCC "trace" #-} when debugPropagate $ traceM (Proxy :: Proxy x) $ liftIO $ do
13881478 invalidators <- liftIO $ readIORef $ holdInvalidators h
13891479 return $ "SubscriberHold" <> showNodeId h <> ": " ++ show (length invalidators)
1480+
13901481 v <- {-# SCC "read" #-} liftIO $ readIORef $ holdValue h
13911482 case {-# SCC "apply" #-} apply a v of
13921483 Nothing -> return ()
13931484 Just v' -> do
1394− {-# SCC "trace2" #-} withIncreasedDepth $ tracePropagate (Proxy :: Proxy x) $ "propagateSubscriberHold: assigning Hold" <> showNodeId h
1485+ {-# SCC "trace2" #-} withIncreasedDepth (Proxy :: Proxy x) $
1486+ tracePropagate (Proxy :: Proxy x) ("propagateSubscriberHold: assigning Hold" <> showNodeId h)
13951487 vRef <- {-# SCC "vRef" #-} liftIO $ evaluate $ holdValue h
13961488 iRef <- {-# SCC "iRef" #-} liftIO $ evaluate $ holdInvalidators h
13971489 defer $ {-# SCC "assignment" #-} SomeAssignment vRef iRef v'
@@ -1445,7 +1537,7 @@ getDynHold d = do
14451537zeroRef :: IORef Height
14461538zeroRef = unsafePerformIO $ newIORef zeroHeight
14471539
1448−getRootSubscribed :: GCompare k => k a -> Root x k -> Subscriber x a -> IO (WeakBagTicket, RootSubscribed x a, Maybe a)
1540+getRootSubscribed :: forall k x a. (GCompare k, HasSpiderTimeline x) => k a -> Root x k -> Subscriber x a -> IO (WeakBagTicket, RootSubscribed x a, Maybe a)
14491541getRootSubscribed k r sub = do
14501542 mSubscribed <- readIORef $ rootSubscribed r
14511543 let getOcc = fmap (coerce . DMap.lookup k) $ readIORef $ rootOccurrence r
@@ -1459,7 +1551,9 @@ getRootSubscribed k r sub = do
14591551 let !cached = rootSubscribed r
14601552 uninitRef <- newIORef $ error "getRootsubscribed: uninitRef not initialized"
14611553 (subs, sln) <- WeakBag.singleton sub weakSelf cleanupRootSubscribed
1462− when debugPropagate $ putStrLn $ "getRootSubscribed: calling rootInit"
1554+
1555+ tracePropagate (Proxy::Proxy x) $ "getRootSubscribed: calling rootInit"
1556+
14631557 uninit <- rootInit r k $ RootTrigger (subs, rootOccurrence r, k)
14641558 writeIORef uninitRef $! uninit
14651559#ifdef DEBUG_NODEIDS
@@ -1509,6 +1603,9 @@ data FanInt x a = FanInt
15091603 { _fanInt_subscribers :: {-# UNPACK #-} !(FastMutableIntMap (FastWeakBag (Subscriber x a))) --TODO: Clean up the keys in here when their child weak bags get empty --TODO: Remove our own subscription when the subscribers list is completely empty
15101604 , _fanInt_subscriptionRef :: {-# UNPACK #-} !(IORef (EventSubscription x)) -- This should have a valid subscription iff subscribers is non-empty
15111605 , _fanInt_occRef :: {-# UNPACK #-} !(IORef (IntMap a))
1606+#ifdef DEBUG_NODEIDS
1607+ , _fanInt_nodeId :: {-# UNPACK #-} !Int
1608+#endif
15121609 }
15131610
15141611newFanInt :: IO (FanInt x a)
@@ -1516,10 +1613,16 @@ newFanInt = do
15161613 subscribers <- FastMutableIntMap.newEmpty --TODO: Clean up the keys in here when their child weak bags get empty --TODO: Remove our own subscription when the subscribers list is completely empty
15171614 subscriptionRef <- newIORef $ error "fanInt: no subscription"
15181615 occRef <- newIORef $ error "fanInt: no occurrence"
1616+#ifdef DEBUG_NODEIDS
1617+ nodeId <- newNodeId
1618+#endif
15191619 return $ FanInt
15201620 { _fanInt_subscribers = subscribers
15211621 , _fanInt_subscriptionRef = subscriptionRef
15221622 , _fanInt_occRef = occRef
1623+#ifdef DEBUG_NODEIDS
1624+ , _fanInt_nodeId = nodeId
1625+#endif
15231626 }
15241627
15251628fanInt :: HasSpiderTimeline x => Event x (IntMap a) -> EventSelectorInt x a
@@ -1528,20 +1631,21 @@ fanInt p = unsafePerformIO $ do
15281631 pure $ EventSelectorInt $ \k -> Event $ \sub -> do
15291632 isEmpty <- liftIO $ FastMutableIntMap.isEmpty (_fanInt_subscribers self)
15301633 when isEmpty $ do -- This is the first subscriber, so we need to subscribe to our input
1531− (subscription, parentOcc) <- subscribeAndRead p $ Subscriber
1634+ let desc = "fanInt" <> showNodeId self <> ", k = " <> show k
1635+ (subscription, parentOcc) <- subscribeAndRead p $ debugSubscriber' desc $ Subscriber
15321636 { subscriberPropagate = \m -> do
15331637 liftIO $ writeIORef (_fanInt_occRef self) m
15341638 scheduleIntClear $ _fanInt_occRef self
1535− FastMutableIntMap.forIntersectionWithImmutable_ (_fanInt_subscribers self) m $ \b v -> do --TODO: Do we need to know that no subscribers are being added as we traverse?
1536− FastWeakBag.traverse_ b $ \s -> do
1639+ FastMutableIntMap.forIntersectionWithImmutable_ (_fanInt_subscribers self) m $ \b v -> --TODO: Do we need to know that no subscribers are being added as we traverse?
1640+ FastWeakBag.traverse_ b $ \s ->
15371641 subscriberPropagate s v
1538− , subscriberInvalidateHeight = \old -> do
1539− FastMutableIntMap.for_ (_fanInt_subscribers self) $ \b -> do
1540− FastWeakBag.traverse_ b $ \s -> do
1642+ , subscriberInvalidateHeight = \old ->
1643+ FastMutableIntMap.for_ (_fanInt_subscribers self) $ \b ->
1644+ FastWeakBag.traverse_ b $ \s ->
15411645 subscriberInvalidateHeight s old
1542− , subscriberRecalculateHeight = \new -> do
1543− FastMutableIntMap.for_ (_fanInt_subscribers self) $ \b -> do
1544− FastWeakBag.traverse_ b $ \s -> do
1646+ , subscriberRecalculateHeight = \new ->
1647+ FastMutableIntMap.for_ (_fanInt_subscribers self) $ \b ->
1648+ FastWeakBag.traverse_ b $ \s ->
15451649 subscriberRecalculateHeight s new
15461650 }
15471651 liftIO $ do
@@ -1555,10 +1659,25 @@ fanInt p = unsafePerformIO $ do
15551659 FastMutableIntMap.insert (_fanInt_subscribers self) k b
15561660 return b
15571661 Just b -> return b
1558− t <- liftIO $ FastWeakBag.insert sub b
1662+ ticket <- liftIO $ FastWeakBag.insert sub b
15591663 currentOcc <- readIORef (_fanInt_occRef self)
1560− (EventSubscription _ (EventSubscribed !heightRef _)) <- readIORef (_fanInt_subscriptionRef self)
1561− return (EventSubscription (FastWeakBag.remove t) $! EventSubscribed heightRef $! toAny (_fanInt_subscriptionRef self, t), IntMap.lookup k currentOcc)
1664+
1665+ subscribed <- fanIntSubscribed ticket self
1666+ return (EventSubscription (FastWeakBag.remove ticket) subscribed, IntMap.lookup k currentOcc)
1667+
1668+fanIntSubscribed :: FastWeakBagTicket k -> FanInt x a -> IO (EventSubscribed x)
1669+fanIntSubscribed ticket self = do
1670+ subscribedParent <- _eventSubscription_subscribed <$> readIORef (_fanInt_subscriptionRef self)
1671+ return $ EventSubscribed
1672+ { eventSubscribedHeightRef = eventSubscribedHeightRef subscribedParent
1673+ , eventSubscribedRetained = toAny (_fanInt_subscriptionRef self, ticket)
1674+#ifdef DEBUG_CYCLES
1675+ , eventSubscribedGetParents = return [subscribedParent]
1676+ , eventSubscribedHasOwnHeightRef = False
1677+ , eventSubscribedWhoCreated = whoCreatedIORef $ _fanInt_subscriptionRef self
1678+#endif
1679+ }
1680+
15621681
15631682{-# INLINABLE getFanSubscribed #-}
15641683getFanSubscribed :: (HasSpiderTimeline x, GCompare k) => k a -> Fan x k v -> Subscriber x (v a) -> EventM x (WeakBagTicket, FanSubscribed x k v, Maybe (v a))
@@ -1756,7 +1875,7 @@ mergeCheap
17561875 => (forall a. q a -> Event x (v a))
17571876 -> DynamicS x (PatchDMap k q)
17581877 -> Event x (DMap k v)
1759−mergeCheap nt = mergeGCheap' getInitialSubscribers updateMe destroy
1878+mergeCheap nt = mergeGCheap' unMergeSubscribedParent getInitialSubscribers updateMe destroy
17601879 where
17611880 updateMe :: MergeUpdateFunc k v x (PatchDMap k q) (MergeSubscribedParent x)
17621881 updateMe subscriber heightBagRef oldParents (PatchDMap p) = do
@@ -1771,7 +1890,9 @@ mergeCheap nt = mergeGCheap' getInitialSubscribers updateMe destroy
17711890 liftIO $ modifyIORef' heightBagRef $ heightBagAdd newParentHeight
17721891 return $ DMap.insertLookupWithKey' (\_ new _ -> new) k newParent ps
17731892 forM_ mOldSubd $ \oldSubd -> do
1774− oldHeight <- liftIO $ getEventSubscribedHeight $ _eventSubscription_subscribed $ unMergeSubscribedParent oldSubd
1893+ oldHeight <- liftIO $ getEventSubscribedHeight $
1894+ _eventSubscription_subscribed $ unMergeSubscribedParent oldSubd
1895+
17751896 liftIO $ modifyIORef heightBagRef $ heightBagRemove oldHeight
17761897 return (maybeToList (unMergeSubscribedParent <$> mOldSubd) ++ subscriptionsToKill, newPs)
17771898 foldM f ([], oldParents) $ DMap.toList p
@@ -1796,7 +1917,7 @@ mergeCheapWithMove :: forall k x v q. (HasSpiderTimeline x, GCompare k)
17961917 => (forall a. q a -> Event x (v a))
17971918 -> DynamicS x (PatchDMapWithMove k q)
17981919 -> Event x (DMap k v)
1799−mergeCheapWithMove nt = mergeGCheap' getInitialSubscribers updateMe destroy
1920+mergeCheapWithMove nt = mergeGCheap' _mergeSubscribedParentWithMove_subscription getInitialSubscribers updateMe destroy
18001921 where
18011922 updateMe :: MergeUpdateFunc k v x (PatchDMapWithMove k q) (MergeSubscribedParentWithMove x k)
18021923 updateMe subscriber heightBagRef oldParents p = do
@@ -1815,13 +1936,17 @@ mergeCheapWithMove nt = mergeGCheap' getInitialSubscribers updateMe destroy
18151936 let moveOrDelete :: forall a. k a -> PatchDMapWithMove.NodeInfo k q a -> MergeSubscribedParentWithMove x k a -> Constant (EventM x (Maybe (EventSubscription x))) a
18161937 moveOrDelete _ ni parent = Constant $ case getComposeMaybe $ PatchDMapWithMove._nodeInfo_to ni of
18171938 Nothing -> do
1818− oldHeight <- liftIO $ getEventSubscribedHeight $ _eventSubscription_subscribed $ _mergeSubscribedParentWithMove_subscription parent
1939+ oldHeight <- liftIO $ getEventSubscribedHeight $ _eventSubscription_subscribed $
1940+ _mergeSubscribedParentWithMove_subscription parent
1941+
18191942 liftIO $ modifyIORef heightBagRef $ heightBagRemove oldHeight
18201943 return $ Just $ _mergeSubscribedParentWithMove_subscription parent
18211944 Just toKey -> do
18221945 liftIO $ writeIORef (_mergeSubscribedParentWithMove_key parent) $! toKey
18231946 return Nothing
1824− toDelete <- fmap catMaybes $ mapM (\(_ :=> v) -> getConstant v) $ DMap.toList $ DMap.intersectionWithKey moveOrDelete (unPatchDMapWithMove p) oldParents
1947+ toDelete <- fmap catMaybes $ mapM (\(_ :=> v) -> getConstant v) $ DMap.toList $
1948+ DMap.intersectionWithKey moveOrDelete (unPatchDMapWithMove p) oldParents
1949+
18251950 return (toDelete, applyAlways p' oldParents)
18261951 getInitialSubscribers :: MergeInitFunc k v q x (MergeSubscribedParentWithMove x k)
18271952 getInitialSubscribers initialParents subscriber = do
@@ -1846,6 +1971,8 @@ type MergeUpdateFunc k v x p s
18461971 -> p
18471972 -> EventM x ([EventSubscription x], DMap k s)
18481973
1974+type MergeGetSubscription x s = forall a. s a -> EventSubscription x
1975+
18491976type MergeInitFunc k v q x s
18501977 = DMap k q
18511978 -> (forall a. EventM x (k a) -> Subscriber x (v a))
@@ -1869,14 +1996,17 @@ invalidateMergeHeight m = invalidateMergeHeight' (_merge_heightRef m) (_merge_su
18691996invalidateMergeHeight' :: IORef Height -> Subscriber x a -> IO ()
18701997invalidateMergeHeight' heightRef sub = do
18711998 oldHeight <- readIORef heightRef
1872− when (oldHeight /= invalidHeight) $ do -- If the height used to be valid, it must be invalid now; we should never have *more* heights than we have parents
1999+ -- If the height used to be valid, it must be invalid now; we should never have *more* heights than we have parents
2000+ when (oldHeight /= invalidHeight) $ do
18732001 writeIORef heightRef $! invalidHeight
18742002 subscriberInvalidateHeight sub oldHeight
18752003
18762004revalidateMergeHeight :: Merge x k v s -> IO ()
18772005revalidateMergeHeight m = do
18782006 currentHeight <- readIORef $ _merge_heightRef m
1879− when (currentHeight == invalidHeight) $ do -- revalidateMergeHeight may be called multiple times; perhaps the's a way to finesse it to avoid this check
2007+
2008+ -- revalidateMergeHeight may be called multiple times; perhaps the's a way to finesse it to avoid this check
2009+ when (currentHeight == invalidHeight) $ do
18802010 heights <- readIORef $ _merge_heightBagRef m
18812011 parents <- readIORef $ _merge_parentsRef m
18822012 -- When the number of heights in the bag reaches the number of parents, we should have a valid height
@@ -1884,7 +2014,7 @@ revalidateMergeHeight m = do
18842014 LT -> return ()
18852015 EQ -> do
18862016 let height = succHeight $ heightBagMax heights
1887− when debugInvalidateHeight $ putStrLn $ "recalculateSubscriberHeight: height: " <> show height
2017+ traceInvalidateHeight $ "recalculateSubscriberHeight: height: " <> show height
18882018 writeIORef (_merge_heightRef m) $! height
18892019 subscriberRecalculateHeight (_merge_sub m) height
18902020 GT -> error $ "revalidateMergeHeight: more heights (" <> show (heightBagSize heights) <> ") than parents (" <> show (DMap.size parents) <> ") for Merge"
@@ -1892,36 +2022,45 @@ revalidateMergeHeight m = do
18922022scheduleMergeSelf :: HasSpiderTimeline x => Merge x k v s -> Height -> EventM x ()
18932023scheduleMergeSelf m height = scheduleMerge' height (_merge_heightRef m) $ do
18942024 vals <- liftIO $ readIORef $ _merge_accumRef m
1895− liftIO $ writeIORef (_merge_accumRef m) $! DMap.empty -- Once we're done with this, we can clear it immediately, because if there's a cacheEvent in front of us, it'll handle subsequent subscribers, and if not, we won't get subsequent subscribers
2025+ -- Once we're done with this, we can clear it immediately, because if there's a cacheEvent in front of us,
2026+ -- it'll handle subsequent subscribers, and if not, we won't get subsequent subscribers
2027+ liftIO $ writeIORef (_merge_accumRef m) $! DMap.empty
18962028 --TODO: Assert that m is not empty
18972029 subscriberPropagate (_merge_sub m) vals
18982030
1899−mergeSubscriber :: forall x k v s a. (HasSpiderTimeline x, GCompare k) => Merge x k v s -> EventM x (k a) -> Subscriber x (v a)
1900−mergeSubscriber m getKey = Subscriber
2031+checkCycle :: HasSpiderTimeline x => EventSubscribed x -> EventM x ()
2032+checkCycle subscribed = liftIO $ do
2033+ height <- readIORef (eventSubscribedHeightRef subscribed)
2034+
2035+ -- currentHeight <- getCurrentHeight
2036+ -- when (height <= currentHeight) $ if height /= invalidHeight
2037+ -- then do
2038+ -- myStack <- liftIO $ whoCreatedIORef undefined --TODO
2039+ -- error $ "Height (" ++ show height ++ ") is not greater than current height (" ++ show currentHeight ++ ")\n" ++ unlines (reverse myStack)
2040+ -- else liftIO $
2041+ when (height == invalidHeight) $
2042+#ifdef DEBUG_CYCLES
2043+ do
2044+ nodesInvolvedInCycle <- walkInvalidHeightParents subscribed
2045+ stacks <- forM nodesInvolvedInCycle whoCreatedEventSubscribed
2046+ throwIO (EventLoopException stacks)
2047+#else
2048+ throwIO EventLoopException
2049+#endif
2050+
2051+
2052+mergeSubscriber :: forall x k v s a. (HasSpiderTimeline x, GCompare k) => EventSubscribed x -> Merge x k v s -> EventM x (k a) -> Subscriber x (v a)
2053+mergeSubscriber subscribed m getKey = Subscriber
19012054 { subscriberPropagate = \a -> do
19022055 oldM <- liftIO $ readIORef $ _merge_accumRef m
19032056 k <- getKey
1904− let newM = DMap.insertWith (error $ "Same key fired multiple times for Merge") k a oldM
2057+ let newM = DMap.insertWith (error "Same key fired multiple times for Merge") k a oldM
19052058 tracePropagate (Proxy :: Proxy x) $ " DMap.size oldM = " <> show (DMap.size oldM) <> "; DMap.size newM = " <> show (DMap.size newM)
19062059 liftIO $ writeIORef (_merge_accumRef m) $! newM
19072060 when (DMap.null oldM) $ do -- Only schedule the firing once
19082061 height <- liftIO $ readIORef $ _merge_heightRef m
1909− --TODO: assertions about height
1910− currentHeight <- getCurrentHeight
1911− when (height <= currentHeight) $ do
1912− if height /= invalidHeight
1913− then do
1914− myStack <- liftIO $ whoCreatedIORef undefined --TODO
1915− error $ "Height (" ++ show height ++ ") is not greater than current height (" ++ show currentHeight ++ ")\n" ++ unlines (reverse myStack)
1916− else liftIO $ do
1917−#ifdef DEBUG_CYCLES
1918− nodesInvolvedInCycle <- walkInvalidHeightParents $ eventSubscribedMerge subscribed
1919− stacks <- forM nodesInvolvedInCycle $ \(Some es) -> whoCreatedEventSubscribed es
1920− let cycleInfo = ":\n" <> drawForest (listsToForest stacks)
1921−#else
1922− let cycleInfo = ""
1923−#endif
1924− error $ "Causality loop found" <> cycleInfo
2062+ checkCycle subscribed
2063+
19252064 scheduleMergeSelf m height
19262065 , subscriberInvalidateHeight = \old -> do --TODO: When removing a parent doesn't actually change the height, maybe we can avoid invalidating
19272066 modifyIORef' (_merge_heightBagRef m) $ heightBagRemove old
@@ -1932,31 +2071,45 @@ mergeSubscriber m getKey = Subscriber
19322071 }
19332072
19342073--TODO: Be able to run as much of this as possible promptly
1935−updateMerge :: (HasSpiderTimeline x, GCompare k) => Merge x k v s -> MergeUpdateFunc k v x p s -> p -> SomeMergeUpdate x
1936−updateMerge m updateFunc p = SomeMergeUpdate updateMe (invalidateMergeHeight m) (revalidateMergeHeight m)
2074+updateMerge :: (HasSpiderTimeline x, GCompare k) => EventSubscribed x -> Merge x k v s -> MergeUpdateFunc k v x p s -> p -> SomeMergeUpdate x
2075+updateMerge subscribed m updateFunc p = SomeMergeUpdate updateMe (invalidateMergeHeight m) (revalidateMergeHeight m)
19372076 where updateMe = do
19382077 oldParents <- liftIO $ readIORef $ _merge_parentsRef m
1939− (subscriptionsToKill, newParents) <- updateFunc (mergeSubscriber m) (_merge_heightBagRef m) oldParents p
2078+ (subscriptionsToKill, newParents) <- updateFunc (mergeSubscriber subscribed m) (_merge_heightBagRef m) oldParents p
19402079 liftIO $ writeIORef (_merge_parentsRef m) $! newParents
19412080 return subscriptionsToKill
19422081
19432082{-# INLINE mergeGCheap' #-}
19442083mergeGCheap' :: forall k v x p s q. (HasSpiderTimeline x, GCompare k, PatchTarget p ~ DMap k q)
1945− => MergeInitFunc k v q x s -> MergeUpdateFunc k v x p s -> MergeDestroyFunc k s -> DynamicS x p -> Event x (DMap k v)
1946−mergeGCheap' getInitialSubscribers updateFunc destroy d = Event $ \sub -> do
2084+ => MergeGetSubscription x s -> MergeInitFunc k v q x s -> MergeUpdateFunc k v x p s -> MergeDestroyFunc k s -> DynamicS x p -> Event x (DMap k v)
2085+mergeGCheap' getParent getInitialSubscribers updateFunc destroy d = Event $ \sub -> do
19472086 initialParents <- readBehaviorUntracked $ dynamicCurrent d
19482087 accumRef <- liftIO $ newIORef $ error "merge: accumRef not yet initialized"
19492088 heightRef <- liftIO $ newIORef $ error "merge: heightRef not yet initialized"
19502089 heightBagRef <- liftIO $ newIORef $ error "merge: heightBagRef not yet initialized"
19512090 parentsRef :: IORef (DMap k s) <- liftIO $ newIORef $ error "merge: parentsRef not yet initialized"
1952− let m = Merge
2091+ changeSubdRef <- liftIO $ newIORef $ error "getMergeSubscribed: changeSubdRef not yet initialized"
2092+
2093+ let subscribed = EventSubscribed
2094+ { eventSubscribedHeightRef = heightRef
2095+ , eventSubscribedRetained = toAny (parentsRef, changeSubdRef)
2096+#ifdef DEBUG_CYCLES
2097+ , eventSubscribedGetParents = do
2098+ let getParent' (_ :=> v) = _eventSubscription_subscribed (getParent v)
2099+ fmap getParent' . DMap.toList <$> readIORef parentsRef
2100+ , eventSubscribedHasOwnHeightRef = False
2101+ , eventSubscribedWhoCreated = whoCreatedIORef heightRef
2102+#endif
2103+ }
2104+
2105+ m = Merge
19532106 { _merge_parentsRef = parentsRef
19542107 , _merge_heightBagRef = heightBagRef
19552108 , _merge_heightRef = heightRef
19562109 , _merge_sub = sub
19572110 , _merge_accumRef = accumRef
19582111 }
1959− (dm, heights, initialParentState) <- getInitialSubscribers initialParents $ mergeSubscriber m
2112+ (dm, heights, initialParentState) <- getInitialSubscribers initialParents $ mergeSubscriber subscribed m
19602113 let myHeightBag = heightBagFromList $ filter (/= invalidHeight) heights
19612114 myHeight = if invalidHeight `elem` heights
19622115 then invalidHeight
@@ -1965,31 +2118,29 @@ mergeGCheap' getInitialSubscribers updateFunc destroy d = Event $ \sub -> do
19652118 let (occ, accum) = if currentHeight >= myHeight -- If we should have fired by now
19662119 then (if DMap.null dm then Nothing else Just dm, DMap.empty)
19672120 else (Nothing, dm)
1968− unless (DMap.null accum) $ do
1969− scheduleMergeSelf m myHeight
2121+ unless (DMap.null accum) $ scheduleMergeSelf m myHeight
19702122 liftIO $ writeIORef accumRef $! accum
19712123 liftIO $ writeIORef heightRef $! myHeight
19722124 liftIO $ writeIORef heightBagRef $! myHeightBag
1973− changeSubdRef <- liftIO $ newIORef $ error "getMergeSubscribed: changeSubdRef not yet initialized"
19742125 liftIO $ writeIORef parentsRef $! initialParentState
19752126 defer $ SomeMergeInit $ do
1976− let s = Subscriber
2127+ let changeSubscriber = Subscriber
19772128 { subscriberPropagate = \a -> {-# SCC "traverseMergeChange" #-} do
1978− tracePropagate (Proxy :: Proxy x) $ "SubscriberMerge/Change"
1979− defer $ updateMerge m updateFunc a
2129+ tracePropagate (Proxy :: Proxy x) "SubscriberMerge/Change"
2130+ defer $ updateMerge subscribed m updateFunc a
19802131 , subscriberInvalidateHeight = \_ -> return ()
19812132 , subscriberRecalculateHeight = \_ -> return ()
19822133 }
1983− (changeSubscription, change) <- subscribeAndRead (dynamicUpdated d) s
1984− forM_ change $ \c -> defer $ updateMerge m updateFunc c
2134+ (changeSubscription, change) <- subscribeAndRead (dynamicUpdated d) changeSubscriber
2135+ forM_ change $ \c -> defer $ updateMerge subscribed m updateFunc c
19852136 -- We explicitly hold on to the unsubscribe function from subscribing to the update event.
19862137 -- If we don't do this, there are certain cases where mergeCheap will fail to properly retain
19872138 -- its subscription.
1988− liftIO $ writeIORef changeSubdRef (s, changeSubscription)
2139+ liftIO $ writeIORef changeSubdRef (changeSubscriber, changeSubscription)
19892140 let unsubscribeAll = destroy =<< readIORef parentsRef
1990− return ( EventSubscription unsubscribeAll $ EventSubscribed heightRef $ toAny (parentsRef, changeSubdRef)
1991− , occ
1992− )
2141+
2142+ return (EventSubscription unsubscribeAll subscribed, occ)
2143+
19932144
19942145mergeInt :: forall x a. (HasSpiderTimeline x) => DynamicS x (PatchIntMap (Event x a)) -> Event x (IntMap a)
19952146mergeInt = cacheEvent . mergeIntCheap
@@ -2002,13 +2153,22 @@ mergeIntCheap d = Event $ \sub -> do
20022153 heightRef <- liftIO $ newIORef zeroHeight
20032154 heightBagRef <- liftIO $ newIORef heightBagEmpty
20042155 parents <- liftIO $ FastMutableIntMap.newEmpty
2156+ changeSubdRef <- liftIO $ newIORef $ error "getMergeSubscribed: changeSubdRef not yet initialized"
2157+ let subscribed = EventSubscribed
2158+ { eventSubscribedHeightRef = heightRef
2159+ , eventSubscribedRetained = toAny (parents, changeSubdRef)
2160+#ifdef DEBUG_CYCLES
2161+ , eventSubscribedGetParents = fmap (_eventSubscription_subscribed . snd) <$> FastMutableIntMap.toList parents
2162+ , eventSubscribedHasOwnHeightRef = False
2163+ , eventSubscribedWhoCreated = whoCreatedIORef heightRef
2164+#endif
2165+ }
20052166 let scheduleSelf = do
20062167 height <- liftIO $ readIORef $ heightRef
20072168 scheduleMerge' height heightRef $ do
20082169 vals <- liftIO $ FastMutableIntMap.getFrozenAndClear accum
20092170 subscriberPropagate sub vals
2010− invalidateMyHeight = do
2011− invalidateMergeHeight' heightRef sub
2171+ invalidateMyHeight = invalidateMergeHeight' heightRef sub
20122172 recalculateMyHeight = do
20132173 currentHeight <- readIORef heightRef
20142174 when (currentHeight == invalidHeight) $ do --TODO: This will almost always be true; can we get rid of this check and just proceed to the next one always?
@@ -2018,12 +2178,14 @@ mergeIntCheap d = Event $ \sub -> do
20182178 LT -> return ()
20192179 EQ -> do
20202180 let height = succHeight $ heightBagMax heights
2021− when debugInvalidateHeight $ putStrLn $ "recalculateSubscriberHeight: height: " <> show height
2181+ traceInvalidateHeight $ "recalculateSubscriberHeight: height: " <> show height
20222182 writeIORef heightRef $! height
20232183 subscriberRecalculateHeight sub height
20242184 GT -> error $ "revalidateMergeHeight: more heights (" <> show (heightBagSize heights) <> ") than parents (" <> show numParents <> ") for Merge"
20252185 mySubscriber k = Subscriber
20262186 { subscriberPropagate = \a -> do
2187+ checkCycle subscribed
2188+
20272189 wasEmpty <- liftIO $ FastMutableIntMap.isEmpty accum
20282190 liftIO $ FastMutableIntMap.insert accum k a
20292191 when wasEmpty scheduleSelf
@@ -2057,7 +2219,6 @@ mergeIntCheap d = Event $ \sub -> do
20572219 else liftIO $ Just <$> FastMutableIntMap.getFrozenAndClear accum
20582220 else do when (not isEmpty) scheduleSelf -- We have things accumulated, but we shouldn't have fired them yet
20592221 return Nothing
2060− changeSubdRef <- liftIO $ newIORef $ error "getMergeSubscribed: changeSubdRef not yet initialized"
20612222 defer $ SomeMergeInit $ do
20622223 let updateMe a = SomeMergeUpdate u invalidateMyHeight recalculateMyHeight
20632224 where
@@ -2071,25 +2232,27 @@ mergeIntCheap d = Event $ \sub -> do
20712232 oldParents <- liftIO $ FastMutableIntMap.applyPatch parents newSubscriptions
20722233 liftIO $ for_ oldParents $ \oldParent -> do
20732234 oldParentHeight <- getEventSubscribedHeight $ _eventSubscription_subscribed oldParent
2235+
2236+ print ("updateMe", oldParentHeight)
20742237 modifyIORef' heightBagRef $ heightBagRemove oldParentHeight
20752238 return $ IntMap.elems oldParents
2076− let s = Subscriber
2239+ let changeSubscriber = Subscriber
20772240 { subscriberPropagate = \a -> {-# SCC "traverseMergeChange" #-} do
20782241 tracePropagate (Proxy :: Proxy x) $ "SubscriberMergeInt/Change"
20792242 defer $ updateMe a
20802243 , subscriberInvalidateHeight = \_ -> return ()
20812244 , subscriberRecalculateHeight = \_ -> return ()
20822245 }
2083− (changeSubscription, change) <- subscribeAndRead (dynamicUpdated d) s
2246+ (changeSubscription, change) <- subscribeAndRead (dynamicUpdated d) changeSubscriber
20842247 forM_ change $ \c -> defer $ updateMe c
20852248 -- We explicitly hold on to the unsubscribe function from subscribing to the update event.
20862249 -- If we don't do this, there are certain cases where mergeCheap will fail to properly retain
20872250 -- its subscription.
2088− liftIO $ writeIORef changeSubdRef (s, changeSubscription)
2251+ liftIO $ writeIORef changeSubdRef (changeSubscriber, changeSubscription)
20892252 let unsubscribeAll = traverse_ unsubscribe =<< FastMutableIntMap.getFrozenAndClear parents
2090− return ( EventSubscription unsubscribeAll $ EventSubscribed heightRef $ toAny (parents, changeSubdRef)
2091− , occ
2092− )
2253+
2254+
2255+ return (EventSubscription unsubscribeAll subscribed, occ)
20932256
20942257newtype EventSelector x k = EventSelector { select :: forall a. k a -> Event x a }
20952258newtype EventSelectorG x k v = EventSelectorG { selectG :: forall a. k a -> Event x (v a) }
@@ -2172,19 +2335,19 @@ runFrame a = SpiderHost $ do
21722335 coincidenceInfos <- readIORef $ eventEnvResetCoincidences env
21732336 forM_ toAssign $ \(SomeAssignment vRef iRef v) -> {-# SCC "assignment" #-} do
21742337 writeIORef vRef v
2175− when debugInvalidate $ putStrLn $ "Invalidating Hold"
2338+ traceInvalidate $ "Invalidating Hold"
21762339 writeIORef iRef =<< evaluate =<< invalidate toReconnectRef =<< readIORef iRef
21772340 mergeUpdates <- readIORef $ eventEnvMergeUpdates env
21782341 writeIORef (eventEnvMergeUpdates env) []
2179− when debugPropagate $ putStrLn "Updating merges"
2342+ tracePropagate (Proxy::Proxy x) $ "Updating merges"
21802343 mergeSubscriptionsToKill <- runEventM $ concat <$> mapM _someMergeUpdate_update mergeUpdates
2181− when debugPropagate $ putStrLn "Updating merges done"
2344+ tracePropagate (Proxy::Proxy x) $ "Updating merges done"
21822345 toReconnect <- readIORef toReconnectRef
21832346 clearEventEnv env
21842347 switchSubscriptionsToKill <- forM toReconnect $ \(SomeSwitchSubscribed subscribed) -> {-# SCC "switchSubscribed" #-} do
21852348 oldSubscription <- readIORef $ switchSubscribedCurrentParent subscribed
21862349 wi <- readIORef $ switchSubscribedOwnWeakInvalidator subscribed
2187− when debugInvalidate $ putStrLn $ "Finalizing invalidator for Switch" <> showNodeId subscribed
2350+ traceInvalidate $ "Finalizing invalidator for Switch" <> showNodeId subscribed
21882351 finalize wi
21892352 i <- evaluate $ switchSubscribedOwnInvalidator subscribed
21902353 wi' <- mkWeakPtrWithDebug i "wi'"
@@ -2198,7 +2361,7 @@ runFrame a = SpiderHost $ do
21982361 subscription <- unSpiderHost $ runFrame $ {-# SCC "subscribeSwitch" #-} subscribe e sub --TODO: Assert that the event isn't firing --TODO: This should not loop because none of the events should be firing, but still, it is inefficient
21992362 {-
22002363 stackTrace <- liftIO $ fmap renderStack $ ccsToStrings =<< (getCCSOf $! switchSubscribedParent subscribed)
2201− liftIO $ putStrLn $ (++stackTrace) $ "subd' subscribed to " ++ case e of
2364+ liftIO $ debugStrLn $ (++stackTrace) $ "subd' subscribed to " ++ case e of
22022365 EventRoot _ -> "EventRoot"
22032366 EventNever -> "EventNever"
22042367 _ -> "something else"
@@ -2490,13 +2653,13 @@ instance HasSpiderTimeline x => Reflex.Host.Class.MonadReadEvent (SpiderTimeline
24902653 touch h
24912654 return result
24922655
2493−instance Reflex.Host.Class.MonadReflexCreateTrigger (SpiderTimeline x) (SpiderHost x) where
2656+instance HasSpiderTimeline x => Reflex.Host.Class.MonadReflexCreateTrigger (SpiderTimeline x) (SpiderHost x) where
24942657 newEventWithTrigger = SpiderHost . fmap SpiderEvent . newEventWithTriggerIO
24952658 newFanEventWithTrigger f = SpiderHost $ do
24962659 es <- newFanEventWithTriggerIO f
24972660 return $ Reflex.Class.EventSelector $ SpiderEvent . Reflex.Spider.Internal.select es
24982661
2499−instance Reflex.Host.Class.MonadReflexCreateTrigger (SpiderTimeline x) (SpiderHostFrame x) where
2662+instance HasSpiderTimeline x => Reflex.Host.Class.MonadReflexCreateTrigger (SpiderTimeline x) (SpiderHostFrame x) where
25002663 newEventWithTrigger = SpiderHostFrame . EventM . liftIO . fmap SpiderEvent . newEventWithTriggerIO
25012664 newFanEventWithTrigger f = SpiderHostFrame $ EventM $ liftIO $ do
25022665 es <- newFanEventWithTriggerIO f
@@ -2682,12 +2845,12 @@ instance NotReady (SpiderTimeline x) (SpiderHostFrame x) where
26822845 notReadyUntil _ = pure ()
26832846 notReady = pure ()
26842847
2685−newEventWithTriggerIO :: (RootTrigger x a -> IO (IO ())) -> IO (Event x a)
2848+newEventWithTriggerIO :: forall x a. HasSpiderTimeline x => (RootTrigger x a -> IO (IO ())) -> IO (Event x a)
26862849newEventWithTriggerIO f = do
26872850 es <- newFanEventWithTriggerIO $ \Refl -> f
26882851 return $ select es Refl
26892852
2690−newFanEventWithTriggerIO :: GCompare k => (forall a. k a -> RootTrigger x a -> IO (IO ())) -> IO (EventSelector x k)
2853+newFanEventWithTriggerIO :: (HasSpiderTimeline x, GCompare k) => (forall a. k a -> RootTrigger x a -> IO (IO ())) -> IO (EventSelector x k)
26912854newFanEventWithTriggerIO f = do
26922855 occRef <- newIORef DMap.empty
26932856 subscribedRef <- newIORef DMap.empty
@@ -2728,4 +2891,4 @@ instance NotReady (SpiderTimeline x) (SpiderHost x) where
27282891
27292892instance HasSpiderTimeline x => NotReady (SpiderTimeline x) (PerformEventT (SpiderTimeline x) (SpiderHost x)) where
27302893 notReadyUntil _ = return ()
2731− notReady = return ()
2894+ notReady = return ()
src/Reflex/TriggerEvent/Class.hs+6
@@ -12,6 +12,7 @@ import Reflex.Class
1212import Control.Monad.Reader
1313import Control.Monad.State
1414import qualified Control.Monad.State.Strict as Strict
15+import Control.Monad.Trans.Maybe (MaybeT)
1516
1617--TODO: Shouldn't have IO hard-coded
1718-- | 'TriggerEvent' represents actions that can create 'Event's that can be
@@ -47,3 +48,8 @@ instance TriggerEvent t m => TriggerEvent t (Strict.StateT s m) where
4748 newTriggerEvent = lift newTriggerEvent
4849 newTriggerEventWithOnComplete = lift newTriggerEventWithOnComplete
4950 newEventWithLazyTriggerWithOnComplete = lift . newEventWithLazyTriggerWithOnComplete
51+
52+instance TriggerEvent t m => TriggerEvent t (MaybeT m) where
53+ newTriggerEvent = lift newTriggerEvent
54+ newTriggerEventWithOnComplete = lift newTriggerEventWithOnComplete
55+ newEventWithLazyTriggerWithOnComplete = lift . newEventWithLazyTriggerWithOnComplete
src/Reflex/Widget/Basic.hs+1−1
@@ -13,7 +13,7 @@ import Data.Map (Map)
1313
1414import Reflex.Class
1515import Reflex.Adjustable.Class
16−import Reflex.Patch.MapWithMove
16+import Data.Patch.MapWithMove
1717
1818
1919-- | Build sortable content in such a way that re-sorting it can cause minimal
test/Adjustable.hsnew file+62
@@ -0,0 +1,62 @@
1+{-# LANGUAGE BangPatterns #-}
2+{-# LANGUAGE LambdaCase #-}
3+{-# LANGUAGE RankNTypes #-}
4+{-# LANGUAGE RecursiveDo #-}
5+
6+module Main where
7+
8+import Control.Monad.Fix
9+import Data.Maybe
10+import qualified Data.Map as Map
11+
12+import Reflex
13+import Reflex.EventWriter.Base
14+import Reflex.Network
15+import Reflex.Patch.MapWithMove
16+import Test.Run
17+
18+main :: IO ()
19+main = do
20+ let actions = [ Increment, Update0th, Increment, Swap, Increment, Increment ]
21+ os <- runAppB testPatchMapWithMove $ map Just actions
22+ -- If the final counter value in the adjusted widgets corresponds to the number of times it has
23+ -- been incremented, we know that the networks haven't broken.
24+ let expectedCount = length $ ffilter (== Increment) actions
25+ -- let !True = last (last os) == [expectedCount,expectedCount] -- TODO re-enable this test after issue #369 has been resolved
26+ return ()
27+
28+data PatchMapTestAction
29+ = Increment
30+ | Swap
31+ | Update0th
32+ deriving Eq
33+
34+-- See https://github.com/reflex-frp/reflex/issues/369 for the bug that this is testing.
35+testPatchMapWithMove
36+ :: forall t m
37+ . ( Reflex t
38+ , Adjustable t m
39+ , MonadHold t m
40+ , MonadFix m
41+ )
42+ => Event t PatchMapTestAction
43+ -> m (Behavior t [Int])
44+testPatchMapWithMove pulse = do
45+ let pulseAction = ffor pulse $ \case
46+ Increment -> Nothing
47+ Swap -> patchMapWithMove $ Map.fromList
48+ [ (0, NodeInfo (From_Move 1) (Just 1))
49+ , (1, NodeInfo (From_Move 0) (Just 0))
50+ ]
51+ Update0th -> patchMapWithMove $ Map.fromList
52+ [ (0, NodeInfo (From_Insert 'z') Nothing) ]
53+ (_, result) <- runBehaviorWriterT $ mdo
54+ counter <- foldDyn (+) 1 $ fmapMaybe (\e -> if isNothing e then Just 1 else Nothing) pulseAction
55+ _ <- mapMapWithAdjustWithMove
56+ (\_ _ -> networkHold
57+ (tellBehavior $ constant [])
58+ ((\t -> tellBehavior $ constant [t]) <$> updated counter))
59+ (Map.fromList $ zip [0..] "ab")
60+ (fmapMaybe id pulseAction)
61+ return ()
62+ return result
test/DebugCycles.hsnew file+171
@@ -0,0 +1,171 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE ConstraintKinds #-}
3+{-# LANGUAGE FlexibleContexts #-}
4+{-# LANGUAGE GADTs #-}
5+{-# LANGUAGE RecursiveDo #-}
6+{-# LANGUAGE ScopedTypeVariables #-}
7+{-# LANGUAGE PatternSynonyms #-}
8+{-# LANGUAGE RankNTypes #-}
9+
10+module Main where
11+
12+import Data.Foldable
13+import Control.Lens
14+import Control.Applicative
15+import Control.Monad
16+import Control.Monad.Fix
17+import Control.Exception
18+import System.Timeout
19+import Data.Maybe (isJust)
20+import Data.Functor.Misc
21+import qualified Data.Map as Map
22+import Data.Map (Map)
23+import qualified Data.IntMap as IntMap
24+import Data.IntMap (IntMap)
25+import Data.These
26+import Data.Align
27+import Reflex
28+import Reflex.EventWriter.Base
29+import Test.Run
30+import Test.Hspec
31+import Reflex.Spider.Internal (EventLoopException)
32+import Data.Witherable (Filterable)
33+
34+#if defined(MIN_VERSION_these_lens) || (MIN_VERSION_these(0,8,0) && !MIN_VERSION_these(0,9,0))
35+import Data.These.Lens
36+#endif
37+
38+type Widget t m = (MonadHold t m, Reflex t, MonadFix m)
39+
40+connectDyn :: Widget t m => Event t () -> (Dynamic t a, Dynamic t a) -> m (Dynamic t a)
41+connectDyn e (d, d') = do
42+ dd <- holdDyn d (d' <$ e)
43+ return $ join dd
44+
45+dynLoop :: Widget t m => (Event t Int, Event t ()) -> m (Event t Int)
46+dynLoop (e1, e2) = do
47+ -- "heightBagRemove: Height 2 not present in bag HeightBag {_heightBag_size = 2, _heightBag_contents = fromList [(0,1)]}"
48+ rec
49+ d <- count e1
50+ d' <- connectDyn e2 (d, liftA2 (+) d d')
51+ return $ updated d'
52+
53+connectOnCoincidence :: Widget t m => Event t () -> Event t a -> m (Event t a)
54+connectOnCoincidence click e = do
55+ d <- holdDyn never (e <$ click)
56+ return $ coincidence (updated d)
57+
58+coincidenceLoop :: Widget t m => (Event t Int, Event t ()) -> m (Event t Int)
59+coincidenceLoop (e1, e2) = do
60+-- "heightBagRemove: Height 1 not present in bag HeightBag {_heightBag_size = 1, _heightBag_contents = fromList [(0,0)]}"
61+-- (simpler version of dynLoop)
62+ rec
63+ e' <- connectOnCoincidence e2 (updated d)
64+ d <- count (align e' e1)
65+ return $ updated d
66+
67+addHeight :: Reflex t => Event t a -> Event t a
68+addHeight e = leftmost [e4, e4] where
69+ e1 = leftmost [e, e]
70+ e2 = leftmost [e1, e1]
71+ e3 = leftmost [e2, e2]
72+ e4 = leftmost [e3, e3]
73+
74+-- Take an existing test and build it inside a
75+buildLoop :: Widget t m => (forall t m. Widget t m => (Event t Int, Event t ()) -> m (Event t Int)) -> (Event t Int, Event t ()) -> m (Event t Int)
76+buildLoop test (e1, e2) = switchHold never buildLoop
77+ where buildLoop = pushAlways (const $ test (e1, e2)) e2
78+
79+connectButtonPromptly :: Widget t m => Event t () -> Event t a -> m (Event t a)
80+connectButtonPromptly click e = do
81+ d <- holdDyn never (e <$ click)
82+ return (switchDyn d)
83+
84+connectButton :: Widget t m => Event t () -> Event t a -> m (Event t a)
85+connectButton click e = do
86+ d <- hold never (e <$ click)
87+ return (switch d)
88+
89+switchLoop01 :: Widget t m => (Event t Int, Event t ()) -> m (Event t Int)
90+switchLoop01 (e1, e2) = do
91+ rec
92+ e' <- connectButton e2 (updated d)
93+ d <- count (align e' e1)
94+ return $ updated d
95+
96+mergeLoop :: forall t m. (Adjustable t m, Widget t m) => (Event t Int, Event t ()) -> m (Event t Int)
97+mergeLoop (e1, e2) = do
98+ rec
99+ (_, e) <- runEventWriterT $
100+ runWithReplace w (leftmost [w <$ e1])
101+ return (sum <$> e)
102+ where
103+ w = do
104+ c <- count e1
105+ tellEvent (updated ((pure <$> c) :: Dynamic t [Int]))
106+
107+switchLoop02 :: Widget t m => (Event t Int, Event t ()) -> m (Event t Int)
108+switchLoop02 (e1, e2) = do
109+ rec
110+ e' <- connectButton e2 (updated d)
111+ d <- count (leftmost [e', e1])
112+ return $ updated d
113+
114+switchLoop03 :: Widget t m => (Event t Int, Event t ()) -> m (Event t Int)
115+switchLoop03 (e1, e2) = do
116+ rec
117+ e' <- connectButton e2 (addHeight $ updated d)
118+ d <- count (align e' e1)
119+ return $ updated d
120+
121+staticLoop01 :: Widget t m => (Event t Int, Event t ()) -> m (Event t Int)
122+staticLoop01 (e1, e2) = do
123+ rec
124+ d <- foldDyn (+) (0 :: Int) (1 <$ align e1 (updated d))
125+ return $ updated d
126+
127+staticLoop02 :: Widget t m => (Event t Int, Event t ()) -> m (Event t Int)
128+staticLoop02 (e1, e2) = do
129+ rec
130+ d <- foldDyn (+) (0 :: Int) (leftmost [e1, updated d])
131+ return $ updated d
132+
133+buildStaticLoop :: Widget t m => (Event t Int, Event t ()) -> m (Event t Int)
134+buildStaticLoop (e1, e2) = switchHold never buildLoop
135+ where buildLoop = pushAlways (const $ staticLoop01 (e1, e2)) e2
136+
137+splitThese :: Filterable f => f (These a b) -> (f a, f b)
138+splitThese f = (mapMaybe (preview here) f, mapMaybe (preview there) f)
139+
140+main :: IO ()
141+main = hspec $ do
142+ describe "DebugCycles" $ do
143+ it "throws EventLoopException on switchLoop01" $ do
144+ check switchLoop01
145+ it "throws EventLoopException on switchLoop02" $ do
146+ check switchLoop02
147+ it "throws EventLoopException on switchLoop03" $ do
148+ check switchLoop03
149+ it "throws EventLoopException on buildSwitchLoop" $ do
150+ check $ buildLoop switchLoop01
151+ xit "throws EventLoopException on mergeLoop" $ do
152+ check mergeLoop
153+ xit "throws EventLoopException on staticLoop01" $ do
154+ check staticLoop01
155+ xit "throws EventLoopException on staticLoop02" $ do
156+ check staticLoop02
157+ xit "throws EventLoopException on buildStaticLoop" $ do
158+ check buildStaticLoop
159+ xit "throws EventLoopException on coincidenceLoop" $ do
160+ check coincidenceLoop
161+ xit "throws EventLoopException on dynLoop" $ do
162+ check dynLoop
163+ xit "throws EventLoopException on buildCoincidenceLoop" $ do
164+ check $ buildLoop coincidenceLoop
165+ where
166+ milliseconds = (*1000)
167+ occs = [ This 1, This 2, That (), This 3, That (), This 1 ]
168+ check test = do
169+ let action = timeout (milliseconds 50) $ do
170+ runApp' (test . splitThese) (Just <$> occs)
171+ action `shouldThrow` (const True :: Selector EventLoopException)
test/GC.hs+1−2
@@ -20,7 +20,7 @@ import Data.Semigroup
2020import Data.These
2121
2222import Data.Functor.Misc
23−import Reflex.Patch
23+import Data.Patch
2424
2525import qualified Reflex.Host.Class as Host
2626import qualified Reflex.Spider.Internal as S
@@ -83,7 +83,6 @@ hostPerf ref = S.runSpiderHost $ do
8383 Nothing -> []
8484 Host.fireEventsAndRead followupEventTriggers $ return ()
8585 return ()
86− return ()
8786
8887data Tell a where
8988 Action :: Tell ()
test/QueryT.hs+1−1
@@ -24,7 +24,7 @@ import Data.These.Lens
2424#endif
2525
2626import Reflex
27−import Reflex.Patch.MapWithMove
27+import Data.Patch.MapWithMove
2828import Test.Run
2929
3030newtype MyQuery = MyQuery SelectedCount
test/Reflex/Test/CrossImpl.hs+12−7
@@ -21,6 +21,7 @@ import Reflex.Dynamic
2121import Reflex.Host.Class
2222import qualified Reflex.Pure as P
2323import qualified Reflex.Spider.Internal as S
24+import qualified Reflex.Profiled as Prof
2425
2526import Control.Arrow (second, (&&&))
2627import Control.Monad.Identity hiding (forM, forM_, mapM, mapM_, sequence, sequence_)
@@ -35,8 +36,6 @@ import Data.Traversable
3536import System.Exit
3637import System.Mem
3738
38−import System.IO.Unsafe
39−
4039mapToPureBehavior :: Map Int a -> Behavior PureReflexDomain a
4140mapToPureBehavior m = P.Behavior $ \t -> case Map.lookupLE t m of
4241 Nothing -> error $ "mapToPureBehavior: no value for time " <> show t
@@ -73,11 +72,14 @@ instance MapMSignals (Event t a) (Event t' a) t t' where
7372instance (MapMSignals a a' t t', MapMSignals b b' t t') => MapMSignals (a, b) (a', b') t t' where
7473 mapMSignals fb fe (a, b) = liftM2 (,) (mapMSignals fb fe a) (mapMSignals fb fe b)
7574
76−testSpider :: (forall m t. TestCaseConstraint t m => (Behavior t a, Event t b) -> m (Behavior t c, Event t d)) -> (Map Int a, Map Int b) -> (Map Int c, Map Int d)
77−testSpider builder (bMap, eMap) = unsafePerformIO $ S.runSpiderHost $ do
75+testTimeline
76+ :: (forall m t. TestCaseConstraint t m => (Behavior t a, Event t b) -> m (Behavior t c, Event t d))
77+ -> (Map Int a, Map Int b)
78+ -> (forall m t. (MonadReflexHost t m, MonadIO m, MonadRef m, Ref m ~ Ref IO, MonadSample t m) => m (Map Int c, Map Int d))
79+testTimeline builder (bMap, eMap) = do
7880 (re, reTrigger) <- newEventWithTriggerRef
7981 (rb, rbTrigger) <- newEventWithTriggerRef
80− b <- runHostFrame $ hold (error "testSpider: No value for input behavior yet") rb
82+ b <- runHostFrame $ hold (error "testTimeline: No value for input behavior yet") rb
8183 (b', e') <- runHostFrame $ builder (b, re)
8284 e'Handle <- subscribeEvent e' --TODO: This should be unnecessary
8385 let times = relevantTestingTimes (bMap, eMap)
@@ -102,9 +104,11 @@ testAgreement :: (Eq c, Eq d, Show c, Show d) => (forall m t. TestCaseConstraint
102104testAgreement builder inputs = do
103105 let identityResult = testPure builder inputs
104106 tracePerf "---------" $ return ()
105− let spiderResult = testSpider builder inputs
107+ spiderResult <- S.runSpiderHost $ testTimeline builder inputs
108+ tracePerf "---------" $ return ()
109+ profiledResult <- S.runSpiderHost $ Prof.runProfiledM $ testTimeline builder inputs
106110 tracePerf "---------" $ return ()
107− let resultsAgree = identityResult == spiderResult
111+ let resultsAgree = identityResult == spiderResult && identityResult == profiledResult
108112 if resultsAgree
109113 then do --putStrLn "Success:"
110114 --print identityResult
@@ -112,6 +116,7 @@ testAgreement builder inputs = do
112116 else do putStrLn "Failure:"
113117 putStrLn $ "Pure result: " <> show identityResult
114118 putStrLn $ "Spider result: " <> show spiderResult
119+ putStrLn $ "Profiled result: " <> show profiledResult
115120 return resultsAgree
116121
117122type TestCaseConstraint t m = (Reflex t, MonadSample t m, MonadHold t m, MonadFix m, MonadFix (PushM t))
test/RequesterT.hs+63−2
@@ -1,20 +1,36 @@
11{-# LANGUAGE CPP #-}
22{-# LANGUAGE BangPatterns #-}
33{-# LANGUAGE FlexibleContexts #-}
4+{-# LANGUAGE FlexibleInstances #-}
45{-# LANGUAGE GADTs #-}
5−{-# LANGUAGE RankNTypes #-}
6+{-# LANGUAGE LambdaCase #-}
7+{-# LANGUAGE MultiParamTypeClasses #-}
68{-# LANGUAGE RecursiveDo #-}
79{-# LANGUAGE ScopedTypeVariables #-}
10+{-# LANGUAGE TemplateHaskell #-}
11+{-# LANGUAGE TypeApplications #-}
12+{-# LANGUAGE TypeFamilies #-}
813module Main where
914
10−import Control.Lens
15+import Control.Lens hiding (has)
1116import Control.Monad
1217import Control.Monad.Fix
18+import Control.Monad.IO.Class (MonadIO)
19+import Data.Constraint.Extras
20+import Data.Constraint.Extras.TH
21+import Data.Constraint.Forall
1322import qualified Data.Dependent.Map as DMap
1423import Data.Dependent.Sum
1524import Data.Functor.Misc
25+import Data.List (words)
26+import Data.Map (Map)
1627import qualified Data.Map as M
28+#if !MIN_VERSION_these(4,11,0)
29+import Data.Semigroup ((<>))
30+#endif
31+import Data.Text (Text)
1732import Data.These
33+import Text.Read (readMaybe)
1834
1935#if defined(MIN_VERSION_these_lens) || (MIN_VERSION_these(0,8,0) && !MIN_VERSION_these(0,9,0))
2036import Data.These.Lens
@@ -49,12 +65,19 @@ main = do
4965 print os5
5066 os6 <- runApp' (unwrapApp delayedPulse) [Just ()]
5167 print os6
68+ os7 <- runApp' testMatchRequestsWithResponses [ Just $ TestRequest_Increment 1 ]
69+ print os7
70+ os8 <- runApp' testMatchRequestsWithResponses [ Just $ TestRequest_Reverse "yoyo" ]
71+ print os8
5272 let ![[Just [1,2,3,4,5,6,7,8,9,10]]] = os1 -- The order is reversed here: see the documentation for 'runRequesterT'
5373 let ![[Just [9,7,5,3,1]],[Nothing,Nothing],[Just [10,8,6,4,2]],[Just [10,8,6,4,2],Nothing]] = os2
5474 let ![[Nothing, Just [2]]] = os3
5575 let ![[Nothing, Just [2]]] = os4
5676 let ![[Nothing, Just [1, 2]]] = os5
5777 -- let ![[Nothing, Nothing]] = os6 -- TODO re-enable this test after issue #233 has been resolved
78+ let !(Just [(1,"2")]) = M.toList <$> head (head os7)
79+ let !(Just [(1,"oyoy")]) = M.toList <$> head (head os8)
80+
5881 return ()
5982
6083unwrapRequest :: DSum tag RequestInt -> Int
@@ -172,3 +195,41 @@ delayedPulse pulse = void $ flip runWithReplace (pure () <$ pulse) $ do
172195 -- This has the effect of delaying pulse' from pulse
173196 (_, pulse') <- runWithReplace (pure ()) $ pure (RequestInt 1) <$ pulse
174197 requestingIdentity pulse'
198+
199+data TestRequest a where
200+ TestRequest_Reverse :: String -> TestRequest String
201+ TestRequest_Increment :: Int -> TestRequest Int
202+
203+testMatchRequestsWithResponses
204+ :: forall m t req a
205+ . ( MonadFix m
206+ , MonadHold t m
207+ , Reflex t
208+ , PerformEvent t m
209+ , MonadIO (Performable m)
210+ , ForallF Show req
211+ , Has Read req
212+ )
213+ => Event t (req a) -> m (Event t (Map Int String))
214+testMatchRequestsWithResponses pulse = mdo
215+ (_, requests) <- runRequesterT (requesting pulse) responses
216+ let rawResponses = M.map (\v ->
217+ case words v of
218+ ["reverse", str] -> reverse str
219+ ["increment", i] -> show $ succ $ (read i :: Int)
220+ ) <$> rawRequestMap
221+ (rawRequestMap, responses) <- matchResponsesWithRequests reqEncoder requests (head . M.toList <$> rawResponses)
222+ pure rawResponses
223+ where
224+ reqEncoder :: forall a. req a -> (String, String -> Maybe a)
225+ reqEncoder r =
226+ ( whichever @Show @req @a $ show r
227+ , \x -> has @Read r $ readMaybe x
228+ )
229+
230+deriveArgDict ''TestRequest
231+
232+instance Show (TestRequest a) where
233+ show = \case
234+ TestRequest_Reverse str -> "reverse " <> str
235+ TestRequest_Increment i -> "increment " <> show i