on develop help sign in
Merge remote-tracking branch 'origin/develop' into patchmapwithmove2
commit49e0bb852fc0640fac19c5f53f72465b0d5e19ad authorRyan Trinkle <ryan.trinkle@gmail.com> dateSun 28 Apr 2019, 10:10 (7 years ago) parentcdf87323ad548b
21 files changed+470−96
CONTRIBUTING.md+57 ChangeLog.md+21 Quickref.md+1−1 README.md+10−5 default.nix+1−1 reflex.cabal+6−3 src/Data/AppendMap.hs+18−9 src/Data/FastMutableIntMap.hs+17 src/Data/FastWeakBag.hs+1−1 src/Reflex/Adjustable/Class.hs+30−3 src/Reflex/Class.hs+103−10 src/Reflex/Dynamic.hs+9−11 src/Reflex/DynamicWriter/Base.hs+2−2 src/Reflex/DynamicWriter/Class.hs+10−4 src/Reflex/EventWriter/Base.hs+2−2 src/Reflex/FunctorMaybe.hs+2 src/Reflex/Query/Base.hs+23−30 src/Reflex/Query/Class.hs+24−4 src/Reflex/Spider/Internal.hs+4−1 test/EventWriterT.hs+18 test/RequesterT.hs+111−9
CONTRIBUTING.mdnew file+57
@@ -0,0 +1,57 @@
1+# Contribution Guide
2+
3+Contributions and issue reports are encouraged and appreciated!
4+
5+- [Opening Issues](#opening-issues)
6+- [Submitting Changes](#submitting-changes)
7+ - [Guidelines for Commit Messages](#guidelines-for-commit-messages)
8+ - [Code Quality](#code-quality)
9+ - [Documentation](#documentation)
10+
11+## Opening Issues
12+
13+Before opening an issue, please check whether your issue has already been reported. Assuming it has not:
14+
15+* Describe the issue you're encountering or the suggestion you're making
16+* Include any relevant steps to reproduce or code samples you can. It's always easier for us to debug if we have something that demonstrates the error.
17+* Let us know what version of reflex you were using. If you're using a github checkout, provide the git hash.
18+* Describe how you're building reflex (i.e., via reflex-platform, cabal install, stack, obelisk, etc.). If you're using reflex-platform or obelisk, provide the git hash of your checkout.
19+
20+## Submitting Changes
21+
22+### Guidelines for Commit Messages
23+
24+#### Summary Line
25+The summary line of your commit message should summarize the changes being made. Commit messages should be written in the imperative mood and should describe what happens when the commit is applied.
26+
27+One way to think about it is that your commit message should be able to complete the sentence:
28+"When applied, this commit will..."
29+
30+#### Body
31+For breaking changes, new features, refactors, or other major changes, the body of the commit message should describe the motivation behind the change in greater detail and may include references to the issue tracker. The body shouldn't repeat code/comments from the diff.
32+
33+### Code Quality
34+
35+#### Warnings
36+
37+Your pull request should add no new warnings to the project. It should also generally not disable any warnings.
38+
39+#### Build and Test
40+
41+Make sure the project builds and that the tests pass! This will generally also be checked by CI before merge, but trying it yourself first means you'll catch problems earlier and your contribution can be merged that much sooner!
42+
43+### Documentation
44+
45+#### In the code
46+We're always striving to improve documentation. Please include [haddock](https://haskell-haddock.readthedocs.io/en/latest/index.html) documentation for any added code, and update the documentation for any code you modify.
47+
48+#### In the [Changelog](ChangeLog.md)
49+Add an entry to the changelog when your PR:
50+* Adds a feature
51+* Deprecates something
52+* Includes a breaking change
53+* Makes any other change that will impact users
54+
55+#### In the [Readme](README.md)
56+The readme is the first place a lot of people look for information about the repository. Update any parts of the readme that are affected by your PR.
57+
ChangeLog.mdnew file+21
@@ -0,0 +1,21 @@
1+# Revision history for reflex
2+
3+## Unreleased
4+
5+* Fix `holdDyn` so that it is lazy in its event argument
6+
7+## 0.6.1.0
8+
9+* Re-export all of `Data.Map.Monoidal`
10+* Fix `QueryT` and `RequesterT` tests
11+
12+## 0.6.0.0 -- 2019-03-20
13+
14+* Deprecate `FunctorMaybe` in favor of `Data.Witherable.Filterable`. We still export `fmapMaybe`, `ffilter`, etc., but they all rely on `Filterable` now.
15+* Rename `MonadDynamicWriter` to `DynamicWriter` and add a deprecation for the old name.
16+* Remove many deprecated functions.
17+* Add a `Num` instance for `Dynamic`.
18+* Add `matchRequestsWithResponses` to make it easier to use `Requester` with protocols that don't do this matching for you.
19+* Add `withRequesterT` to map functions over the request and response of a `RequesterT`.
20+* Suppress nil patches in `QueryT` as an optimization. The `Query` type must now have an `Eq` instance.
21+* Add `throttleBatchWithLag` to `Reflex.Time`. See that module for details.
Quickref.md+1−1
@@ -166,7 +166,7 @@ For Events, the returned Event fires whenever the latest Event supplied by the w
166166-- Flatten Behavior-of-Event to Event. Old Event is used during switchover.
167167[ ] switch :: Behavior (Event a) -> Event a
168168
169−-- Flatten Dyanmic-of-Event to Event. New Event is used immediately.
169+-- Flatten Dynamic-of-Event to Event. New Event is used immediately.
170170[ ] switchDyn :: Dynamic (Event a) -> Event a
171171
172172-- Flatten Event-of-Event to Event that fires when both wrapper AND new Event fire.
README.md+10−5
@@ -1,15 +1,15 @@
1−## Reflex
1+## [Reflex](https://reflex-frp.org/)
22### Practical Functional Reactive Programming
33
4−Reflex is an fully-deterministic, higher-order Functional Reactive Programming (FRP) interface and an engine that efficiently implements that interface.
4+Reflex is a fully-deterministic, higher-order Functional Reactive Programming (FRP) interface and an engine that efficiently implements that interface.
55
66[Reflex-DOM](https://github.com/reflex-frp/reflex-dom) is a framework built on Reflex that facilitates the development of web pages, including highly-interactive single-page apps.
77
8−Comprehensive documentation is still a work in progress, but a tutorial for Reflex and Reflex-DOM is available at https://github.com/reflex-frp/reflex-platform and an introductory talk given at the New York Haskell Meetup is available here: [Part 1](https://www.youtube.com/watch?v=mYvkcskJbc4) / [Part 2](https://www.youtube.com/watch?v=3qfc9XFVo2c).
9−
108A summary of Reflex functions is available in the [quick reference](Quickref.md).
119
12−### Additional resources
10+**Visit https://reflex-frp.org/ for more information, tutorials, documentation and [examples](https://examples.reflex-frp.org/).**
11+
12+### Resources
1313[Get started with Reflex](https://github.com/reflex-frp/reflex-platform)
1414
1515[/r/reflexfrp](https://www.reddit.com/r/reflexfrp)
@@ -17,3 +17,8 @@ A summary of Reflex functions is available in the [quick reference](Quickref.md)
1717[hackage](https://hackage.haskell.org/package/reflex)
1818
1919[irc.freenode.net #reflex-frp](http://webchat.freenode.net?channels=%23reflex-frp&uio=d4)
20+
21+### Hacking
22+
23+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.
24+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.
default.nix+1−1
@@ -11,7 +11,7 @@
1111}:
1212mkDerivation {
1313 pname = "reflex";
14− version = "0.5.0.1";
14+ version = "0.6.1";
1515 src = builtins.filterSource (path: type: !(builtins.elem (baseNameOf path) [ ".git" "dist" ])) ./.;
1616 libraryHaskellDepends = [
1717 base bifunctors containers dependent-map dependent-sum
reflex.cabal+6−3
@@ -1,5 +1,5 @@
11Name: reflex
2−Version: 0.6
2+Version: 0.6.1
33Synopsis: Higher-order Functional Reactive Programming
44Description: Reflex is a high-performance, deterministic, higher-order Functional Reactive Programming system
55License: BSD3
@@ -15,6 +15,7 @@ bug-reports: https://github.com/reflex-frp/reflex/issues
1515extra-source-files:
1616 README.md
1717 Quickref.md
18+ ChangeLog.md
1819
1920flag use-reflex-optimizer
2021 description: Use the GHC plugin Reflex.Optimizer on some of the modules in the package. This is still experimental.
@@ -60,7 +61,7 @@ library
6061 semigroups >= 0.16 && < 0.19,
6162 stm >= 2.4 && < 2.6,
6263 syb >= 0.5 && < 0.8,
63− these >= 0.4 && < 0.7.7,
64+ these >= 0.4 && < 0.9,
6465 time >= 1.4 && < 1.9,
6566 transformers >= 0.2,
6667 transformers-compat >= 0.3,
@@ -235,14 +236,16 @@ test-suite RequesterT
235236 main-is: RequesterT.hs
236237 hs-source-dirs: test
237238 build-depends: base
239+ , containers
240+ , deepseq >= 1.3 && < 1.5
238241 , dependent-sum
239242 , dependent-map
240243 , lens
244+ , mtl
241245 , these
242246 , transformers
243247 , reflex
244248 , ref-tf
245− buildable: False
246249 other-modules:
247250 Reflex.TestPlan
248251 Reflex.Plan.Pure
src/Data/AppendMap.hs+18−9
@@ -7,10 +7,14 @@
77{-# LANGUAGE ScopedTypeVariables #-}
88{-# LANGUAGE TypeFamilies #-}
99{-# OPTIONS_GHC -fno-warn-orphans #-}
10−-- | 'Data.Map' with a better 'Monoid' instance
11−--
12−-- 'Data.Map' has @mappend = union@, which is left-biased. AppendMap has
13−-- @mappend = unionWith mappend@ instead.
10+-- |
11+-- Module:
12+-- Data.AppendMap
13+-- Description:
14+-- Instances and convenience functions for 'Data.Map.Monoidal'. We use
15+-- monoidal-containers to take advantage of its better monoid instance.
16+-- 'Data.Map' has @mappend = union@, which is left-biased. 'MonoidalMap'
17+-- has @mappend = unionWith mappend@ instead.
1418module Data.AppendMap
1519 ( module Data.AppendMap
1620 , module Data.Map.Monoidal
@@ -26,22 +30,24 @@ import qualified Data.Map.Internal.Debug as Map (showTree, showTreeWith)
2630#else
2731import qualified Data.Map as Map (showTree, showTreeWith)
2832#endif
29−import Data.Witherable (Filterable(..))
30−import Data.Map.Monoidal (MonoidalMap(..), delete, null, empty)
31−import qualified Data.Map.Monoidal as M
33+import qualified Data.Witherable as W
34+import Data.Map.Monoidal
3235
3336{-# DEPRECATED AppendMap "Use 'MonoidalMap' instead" #-}
37+-- | AppendMap is a synonym for 'Data.Map.Monoidal.MonoidalMap'
3438type AppendMap = MonoidalMap
3539
3640{-# DEPRECATED _unAppendMap "Use 'getMonoidalMap' instead" #-}
41+-- | A synonym for 'getMonoidalMap'
3742_unAppendMap :: MonoidalMap k v -> Map k v
3843_unAppendMap = getMonoidalMap
3944
45+-- | Pattern synonym for 'MonoidalMap'
4046pattern AppendMap :: Map k v -> MonoidalMap k v
4147pattern AppendMap m = MonoidalMap m
4248
43−instance Filterable (MonoidalMap k) where
44− mapMaybe = M.mapMaybe
49+instance W.Filterable (MonoidalMap k) where
50+ mapMaybe = mapMaybe
4551
4652-- | Deletes a key, returning 'Nothing' if the result is empty.
4753nonEmptyDelete :: Ord k => k -> MonoidalMap k a -> Maybe (MonoidalMap k a)
@@ -51,6 +57,7 @@ nonEmptyDelete k vs =
5157 then Nothing
5258 else Just deleted
5359
60+-- | Like 'mapMaybe' but indicates whether the resulting container is empty
5461mapMaybeNoNull :: (a -> Maybe b)
5562 -> MonoidalMap token a
5663 -> Maybe (MonoidalMap token b)
@@ -61,9 +68,11 @@ mapMaybeNoNull f as =
6168 else Just bs
6269
6370-- TODO: Move instances to `Reflex.Patch`
71+-- | Displays a 'MonoidalMap' as a tree. See 'Data.Map.Lazy.showTree' for details.
6472showTree :: forall k a. (Show k, Show a) => MonoidalMap k a -> String
6573showTree = coerce (Map.showTree :: Map k a -> String)
6674
75+-- | Displays a 'MonoidalMap' as a tree, using the supplied function to convert nodes to string.
6776showTreeWith :: forall k a. (k -> a -> String) -> Bool -> Bool -> MonoidalMap k a -> String
6877showTreeWith = coerce (Map.showTreeWith :: (k -> a -> String) -> Bool -> Bool -> Map k a -> String)
6978
src/Data/FastMutableIntMap.hs+17
@@ -1,4 +1,9 @@
11{-# LANGUAGE TypeFamilies #-}
2+-- |
3+-- Module:
4+-- Data.FastMutableIntMap
5+-- Description:
6+-- A mutable version of 'IntMap'
27module Data.FastMutableIntMap
38 ( FastMutableIntMap
49 , new
@@ -32,33 +37,43 @@ import Data.IORef
3237import Reflex.Patch.Class
3338import Reflex.Patch.IntMap
3439
40+-- | A 'FastMutableIntMap' holds a map of values of type @a@ and allows low-overhead modifications via IO.
41+-- Operations on 'FastMutableIntMap' run in IO.
3542newtype FastMutableIntMap a = FastMutableIntMap (IORef (IntMap a))
3643
44+-- | Create a new 'FastMutableIntMap' out of an 'IntMap'
3745new :: IntMap a -> IO (FastMutableIntMap a)
3846new m = FastMutableIntMap <$> newIORef m
3947
48+-- | Create a new empty 'FastMutableIntMap'
4049newEmpty :: IO (FastMutableIntMap a)
4150newEmpty = FastMutableIntMap <$> newIORef IntMap.empty
4251
52+-- | Insert an element into a 'FastMutableIntMap' at the given key
4353insert :: FastMutableIntMap a -> Int -> a -> IO ()
4454insert (FastMutableIntMap r) k v = modifyIORef' r $ IntMap.insert k v
4555
56+-- | Attempt to lookup an element by key in a 'FastMutableIntMap'
4657lookup :: FastMutableIntMap a -> Int -> IO (Maybe a)
4758lookup (FastMutableIntMap r) k = IntMap.lookup k <$> readIORef r
4859
60+-- | Runs the provided action over the intersection of a 'FastMutableIntMap' and an 'IntMap'
4961forIntersectionWithImmutable_ :: MonadIO m => FastMutableIntMap a -> IntMap b -> (a -> b -> m ()) -> m ()
5062forIntersectionWithImmutable_ (FastMutableIntMap r) b f = do
5163 a <- liftIO $ readIORef r
5264 traverse_ (uncurry f) $ IntMap.intersectionWith (,) a b
5365
66+-- | Runs the provided action over the values of a 'FastMutableIntMap'
5467for_ :: MonadIO m => FastMutableIntMap a -> (a -> m ()) -> m ()
5568for_ (FastMutableIntMap r) f = do
5669 a <- liftIO $ readIORef r
5770 traverse_ f a
5871
72+-- | Checks whether a 'FastMutableIntMap' is empty
5973isEmpty :: FastMutableIntMap a -> IO Bool
6074isEmpty (FastMutableIntMap r) = IntMap.null <$> readIORef r
6175
76+-- | Retrieves the size of a 'FastMutableIntMap'
6277size :: FastMutableIntMap a -> IO Int
6378size (FastMutableIntMap r) = IntMap.size <$> readIORef r
6479
@@ -69,6 +84,8 @@ getFrozenAndClear (FastMutableIntMap r) = do
6984 writeIORef r IntMap.empty
7085 return result
7186
87+-- | Updates the value of a 'FastMutableIntMap' with the given patch (see 'Reflex.Patch.IntMap'),
88+-- and returns an 'IntMap' with the modified keys and values.
7289applyPatch :: FastMutableIntMap a -> PatchIntMap a -> IO (IntMap a)
7390applyPatch (FastMutableIntMap r) p@(PatchIntMap m) = do
7491 v <- readIORef r
src/Data/FastWeakBag.hs+1−1
@@ -50,7 +50,7 @@ newtype FastWeakBag a = FastWeakBag JSVal
5050#else
5151data FastWeakBag a = FastWeakBag
5252 { _weakBag_nextId :: {-# UNPACK #-} !(IORef Int) --TODO: what if this wraps around?
53− , _weakBag_children :: {-# UNPACK #-} !(IORef (IntMap (Weak a)))
53+ , _weakBag_children :: {-# UNPACK #-} !(IORef (IntMap (Weak a))) -- ^ Map of items contained by the 'FastWeakBag'
5454 }
5555#endif
5656
src/Reflex/Adjustable/Class.hs+30−3
@@ -11,6 +11,13 @@
1111#ifdef USE_REFLEX_OPTIMIZER
1212{-# OPTIONS_GHC -fplugin=Reflex.Optimizer #-}
1313#endif
14+-- |
15+-- Module:
16+-- Reflex.Adjustable.Class
17+-- Description:
18+-- A class for actions that can be "adjusted" over time based on some 'Event'
19+-- such that, when observed after the firing of any such 'Event', the result
20+-- is as though the action was originally run with the 'Event's value.
1421module Reflex.Adjustable.Class
1522 (
1623 -- * The Adjustable typeclass
@@ -94,13 +101,32 @@ instance Adjustable t m => Adjustable t (ReaderT r m) where
94101 r <- ask
95102 lift $ traverseDMapWithKeyWithAdjustWithMove (\k v -> runReaderT (f k v) r) dm0 dm'
96103
97−sequenceDMapWithAdjust :: (GCompare k, Adjustable t m) => DMap k m -> Event t (PatchDMap k m) -> m (DMap k Identity, Event t (PatchDMap k Identity))
104+-- | Traverse a 'DMap' of 'Adjustable' actions, running each of them. The provided 'Event' of patches
105+-- to the 'DMap' can add, remove, or update values.
106+sequenceDMapWithAdjust
107+ :: (GCompare k, Adjustable t m)
108+ => DMap k m
109+ -> Event t (PatchDMap k m)
110+ -> m (DMap k Identity, Event t (PatchDMap k Identity))
98111sequenceDMapWithAdjust = traverseDMapWithKeyWithAdjust $ \_ -> fmap Identity
99112
100−sequenceDMapWithAdjustWithMove :: (GCompare k, Adjustable t m) => DMap k m -> Event t (PatchDMapWithMove k m) -> m (DMap k Identity, Event t (PatchDMapWithMove k Identity))
113+-- | Traverses a 'DMap' of 'Adjustable' actions, running each of them. The provided 'Event' of patches
114+-- to the 'DMap' can add, remove, update, move, or swap values.
115+sequenceDMapWithAdjustWithMove
116+ :: (GCompare k, Adjustable t m)
117+ => DMap k m
118+ -> Event t (PatchDMapWithMove k m)
119+ -> m (DMap k Identity, Event t (PatchDMapWithMove k Identity))
101120sequenceDMapWithAdjustWithMove = traverseDMapWithKeyWithAdjustWithMove $ \_ -> fmap Identity
102121
103−mapMapWithAdjustWithMove :: forall t m k v v'. (Adjustable t m, Ord k) => (k -> v -> m v') -> Map k v -> Event t (PatchMapWithMove k v) -> m (Map k v', Event t (PatchMapWithMove k v'))
122+-- | Traverses a 'Map', running the provided 'Adjustable' action. The provided 'Event' of patches to the 'Map'
123+-- can add, remove, update, move, or swap values.
124+mapMapWithAdjustWithMove
125+ :: forall t m k v v'. (Adjustable t m, Ord k)
126+ => (k -> v -> m v')
127+ -> Map k v
128+ -> Event t (PatchMapWithMove k v)
129+ -> m (Map k v', Event t (PatchMapWithMove k v'))
104130mapMapWithAdjustWithMove f m0 m' = do
105131 (out0 :: DMap (Const2 k v) (Constant v'), out') <- traverseDMapWithKeyWithAdjustWithMove (\(Const2 k) (Identity v) -> Constant <$> f k v) (mapToDMap m0) (const2PatchDMapWithMoveWith Identity <$> m')
106132 return (dmapToMapWith (\(Constant v') -> v') out0, patchDMapWithMoveToPatchMapWithMoveWith (\(Constant v') -> v') <$> out')
@@ -110,4 +136,5 @@ mapMapWithAdjustWithMove f m0 m' = do
110136--------------------------------------------------------------------------------
111137
112138{-# DEPRECATED MonadAdjust "Use Adjustable instead" #-}
139+-- | Synonym for 'Adjustable'
113140type MonadAdjust = Adjustable
src/Reflex/Class.hs+103−10
@@ -300,6 +300,8 @@ class ( MonadHold t (PushM t)
300300 fanInt :: Event t (IntMap a) -> EventSelectorInt t a
301301
302302--TODO: Specialize this so that we can take advantage of knowing that there's no changing going on
303+-- | Constructs a single 'Event' out of a map of events. The output event may fire with multiple
304+-- keys simultaneously.
303305mergeInt :: Reflex t => IntMap (Event t a) -> Event t (IntMap a)
304306mergeInt m = mergeIntIncremental $ unsafeBuildIncremental (return m) never
305307
@@ -368,26 +370,91 @@ class MonadSample t m => MonadHold t m where
368370 -- the supplied 'Event'.
369371 headE :: Event t a -> m (Event t a)
370372
371−accumIncremental :: (Reflex t, Patch p, MonadHold t m, MonadFix m) => (PatchTarget p -> b -> p) -> PatchTarget p -> Event t b -> m (Incremental t p)
373+-- | Accumulate an 'Incremental' with the supplied initial value and the firings of the provided 'Event',
374+-- using the combining function to produce a patch.
375+accumIncremental
376+ :: (Reflex t, Patch p, MonadHold t m, MonadFix m)
377+ => (PatchTarget p -> b -> p)
378+ -> PatchTarget p
379+ -> Event t b
380+ -> m (Incremental t p)
372381accumIncremental f = accumMaybeIncremental $ \v o -> Just $ f v o
373−accumMIncremental :: (Reflex t, Patch p, MonadHold t m, MonadFix m) => (PatchTarget p -> b -> PushM t p) -> PatchTarget p -> Event t b -> m (Incremental t p)
382+
383+-- | Similar to 'accumIncremental' but the combining function runs in 'PushM'
384+accumMIncremental
385+ :: (Reflex t, Patch p, MonadHold t m, MonadFix m)
386+ => (PatchTarget p -> b -> PushM t p)
387+ -> PatchTarget p
388+ -> Event t b
389+ -> m (Incremental t p)
374390accumMIncremental f = accumMaybeMIncremental $ \v o -> Just <$> f v o
375−accumMaybeIncremental :: (Reflex t, Patch p, MonadHold t m, MonadFix m) => (PatchTarget p -> b -> Maybe p) -> PatchTarget p -> Event t b -> m (Incremental t p)
391+
392+-- | Similar to 'accumIncremental' but allows filtering of updates (by dropping updates when the
393+-- combining function produces @Nothing@)
394+accumMaybeIncremental
395+ :: (Reflex t, Patch p, MonadHold t m, MonadFix m)
396+ => (PatchTarget p -> b -> Maybe p)
397+ -> PatchTarget p
398+ -> Event t b
399+ -> m (Incremental t p)
376400accumMaybeIncremental f = accumMaybeMIncremental $ \v o -> return $ f v o
377−accumMaybeMIncremental :: (Reflex t, Patch p, MonadHold t m, MonadFix m) => (PatchTarget p -> b -> PushM t (Maybe p)) -> PatchTarget p -> Event t b -> m (Incremental t p)
401+
402+-- | Similar to 'accumMaybeMIncremental' but the combining function runs in 'PushM'
403+accumMaybeMIncremental
404+ :: (Reflex t, Patch p, MonadHold t m, MonadFix m)
405+ => (PatchTarget p -> b -> PushM t (Maybe p))
406+ -> PatchTarget p
407+ -> Event t b
408+ -> m (Incremental t p)
378409accumMaybeMIncremental f z e = do
379410 rec let e' = flip push e $ \o -> do
380411 v <- sample $ currentIncremental d'
381412 f v o
382413 d' <- holdIncremental z e'
383414 return d'
384−mapAccumIncremental :: (Reflex t, Patch p, MonadHold t m, MonadFix m) => (PatchTarget p -> b -> (p, c)) -> PatchTarget p -> Event t b -> m (Incremental t p, Event t c)
415+
416+-- | Accumulate an 'Incremental' by folding occurrences of an 'Event'
417+-- with a function that both accumulates and produces a value to fire
418+-- as an 'Event'. Returns both the accumulated value and the constructed
419+-- 'Event'.
420+mapAccumIncremental
421+ :: (Reflex t, Patch p, MonadHold t m, MonadFix m)
422+ => (PatchTarget p -> b -> (p, c))
423+ -> PatchTarget p
424+ -> Event t b
425+ -> m (Incremental t p, Event t c)
385426mapAccumIncremental f = mapAccumMaybeIncremental $ \v o -> bimap Just Just $ f v o
386−mapAccumMIncremental :: (Reflex t, Patch p, MonadHold t m, MonadFix m) => (PatchTarget p -> b -> PushM t (p, c)) -> PatchTarget p -> Event t b -> m (Incremental t p, Event t c)
427+
428+-- | Like 'mapAccumIncremental' but the combining function runs in 'PushM'
429+mapAccumMIncremental
430+ :: (Reflex t, Patch p, MonadHold t m, MonadFix m)
431+ => (PatchTarget p -> b -> PushM t (p, c))
432+ -> PatchTarget p
433+ -> Event t b
434+ -> m (Incremental t p, Event t c)
387435mapAccumMIncremental f = mapAccumMaybeMIncremental $ \v o -> bimap Just Just <$> f v o
388−mapAccumMaybeIncremental :: (Reflex t, Patch p, MonadHold t m, MonadFix m) => (PatchTarget p -> b -> (Maybe p, Maybe c)) -> PatchTarget p -> Event t b -> m (Incremental t p, Event t c)
436+
437+-- | Accumulate an 'Incremental' by folding occurrences of an 'Event' with
438+-- a function that both optionally accumulates and optionally produces
439+-- a value to fire as a separate output 'Event'.
440+-- Note that because 'Nothing's are discarded in both cases, the output
441+-- 'Event' may fire even though the output 'Incremental' has not changed, and
442+-- the output 'Incremental' may update even when the output 'Event' is not firing.
443+mapAccumMaybeIncremental
444+ :: (Reflex t, Patch p, MonadHold t m, MonadFix m)
445+ => (PatchTarget p -> b -> (Maybe p, Maybe c))
446+ -> PatchTarget p
447+ -> Event t b
448+ -> m (Incremental t p, Event t c)
389449mapAccumMaybeIncremental f = mapAccumMaybeMIncremental $ \v o -> return $ f v o
390−mapAccumMaybeMIncremental :: (Reflex t, Patch p, MonadHold t m, MonadFix m) => (PatchTarget p -> b -> PushM t (Maybe p, Maybe c)) -> PatchTarget p -> Event t b -> m (Incremental t p, Event t c)
450+
451+-- | Like 'mapAccumMaybeIncremental' but the combining function is a 'PushM' action
452+mapAccumMaybeMIncremental
453+ :: (Reflex t, Patch p, MonadHold t m, MonadFix m)
454+ => (PatchTarget p -> b -> PushM t (Maybe p, Maybe c))
455+ -> PatchTarget p
456+ -> Event t b
457+ -> m (Incremental t p, Event t c)
391458mapAccumMaybeMIncremental f z e = do
392459 rec let e' = flip push e $ \o -> do
393460 v <- sample $ currentIncremental d'
@@ -398,6 +465,7 @@ mapAccumMaybeMIncremental f z e = do
398465 d' <- holdIncremental z $ mapMaybe fst e'
399466 return (d', mapMaybe snd e')
400467
468+-- | A somewhat slow implementation of 'headE'
401469slowHeadE :: (Reflex t, MonadHold t m, MonadFix m) => Event t a -> m (Event t a)
402470slowHeadE e = do
403471 rec be <- hold e $ fmapCheap (const never) e'
@@ -418,6 +486,8 @@ newtype EventSelector t k = EventSelector
418486 select :: forall a. k a -> Event t a
419487 }
420488
489+-- | Efficiently select an 'Event' keyed on 'Int'. This is more efficient than manually
490+-- filtering by key.
421491newtype EventSelectorInt t a = EventSelectorInt { selectInt :: Int -> Event t a }
422492
423493--------------------------------------------------------------------------------
@@ -801,7 +871,12 @@ mergeList :: Reflex t => [Event t a] -> Event t (NonEmpty a)
801871mergeList [] = never
802872mergeList es = mergeWithFoldCheap' id es
803873
804−unsafeMapIncremental :: (Reflex t, Patch p, Patch p') => (PatchTarget p -> PatchTarget p') -> (p -> p') -> Incremental t p -> Incremental t p'
874+unsafeMapIncremental
875+ :: (Reflex t, Patch p, Patch p')
876+ => (PatchTarget p -> PatchTarget p')
877+ -> (p -> p')
878+ -> Incremental t p
879+ -> Incremental t p'
805880unsafeMapIncremental f g a = unsafeBuildIncremental (fmap f $ sample $ currentIncremental a) $ g <$> updatedIncremental a
806881
807882-- | Create a new 'Event' combining the map of 'Event's into an 'Event' that
@@ -918,6 +993,9 @@ coincidencePatchMapWithMove e = fmapCheap unsafePatchMapWithMove $ coincidence $
918993 ni { PatchMapWithMove._nodeInfo_from = PatchMapWithMove.From_Delete }
919994 ]
920995
996+-- | Given a 'PatchTarget' of events (e.g., a 'Map' with 'Event' values) and an event of 'Patch'es
997+-- (e.g., a 'PatchMap' with 'Event' values), produce an 'Event' of the 'PatchTarget' type that
998+-- fires with the patched value.
921999switchHoldPromptOnlyIncremental
9221000 :: forall t m p pt w
9231001 . ( Reflex t
@@ -942,8 +1020,12 @@ switchHoldPromptOnlyIncremental mergePatchIncremental coincidencePatch e0 e' = d
9421020
9431021instance Reflex t => Align (Event t) where
9441022 nil = never
1023+#if MIN_VERSION_these(0, 8, 0)
1024+instance Reflex t => Semialign (Event t) where
1025+#endif
9451026 align = alignEventWithMaybe Just
9461027
1028+
9471029-- | Create a new 'Event' that only occurs if the supplied 'Event' occurs and
9481030-- the 'Behavior' is true at the time of occurrence.
9491031gate :: Reflex t => Behavior t Bool -> Event t a -> Event t a
@@ -1015,7 +1097,16 @@ distributeListOverDyn = distributeListOverDynWith id
10151097
10161098-- | Create a new 'Dynamic' by applying a combining function to a list of 'Dynamic's
10171099distributeListOverDynWith :: Reflex t => ([a] -> b) -> [Dynamic t a] -> Dynamic t b
1018−distributeListOverDynWith f = fmap (f . map (\(Const2 _ :=> Identity v) -> v) . DMap.toList) . distributeDMapOverDynPure . DMap.fromList . map (\(k, v) -> Const2 k :=> v) . zip [0 :: Int ..]
1100+distributeListOverDynWith f =
1101+ fmap (f . map fromDSum . DMap.toAscList) .
1102+ distributeDMapOverDynPure .
1103+ DMap.fromDistinctAscList .
1104+ zipWith toDSum [0..]
1105+ where
1106+ toDSum :: Int -> Dynamic t a -> DSum (Const2 Int a) (Dynamic t)
1107+ toDSum k v = Const2 k :=> v
1108+ fromDSum :: DSum (Const2 Int a) Identity -> a
1109+ fromDSum (Const2 _ :=> Identity v) = v
10191110
10201111-- | Create a new 'Event' that occurs when the first supplied 'Event' occurs
10211112-- unless the second supplied 'Event' occurs simultaneously.
@@ -1491,8 +1582,10 @@ mergeWithFoldCheap' f es =
14911582--------------------------------------------------------------------------------
14921583
14931584{-# DEPRECATED switchPromptly "Use 'switchHoldPromptly' instead. The 'switchHold*' naming convention was chosen because those functions are more closely related to each other than they are to 'switch'. " #-}
1585+-- | See 'switchHoldPromptly'
14941586switchPromptly :: (Reflex t, MonadHold t m) => Event t a -> Event t (Event t a) -> m (Event t a)
14951587switchPromptly = switchHoldPromptly
14961588{-# DEPRECATED switchPromptOnly "Use 'switchHoldPromptOnly' instead. The 'switchHold*' naming convention was chosen because those functions are more closely related to each other than they are to 'switch'. " #-}
1589+-- | See 'switchHoldPromptOnly'
14971590switchPromptOnly :: (Reflex t, MonadHold t m) => Event t a -> Event t (Event t a) -> m (Event t a)
14981591switchPromptOnly = switchHoldPromptOnly
src/Reflex/Dynamic.hs+9−11
@@ -203,23 +203,15 @@ distributeMapOverDynPure = fmap dmapToMap . distributeDMapOverDynPure . mapWithF
203203
204204-- | Convert a list with 'Dynamic' elements into a 'Dynamic' of a list with
205205-- non-'Dynamic' elements, preserving the order of the elements.
206+{-# DEPRECATED distributeListOverDynPure "Use 'distributeListOverDyn' instead" #-}
206207distributeListOverDynPure :: Reflex t => [Dynamic t v] -> Dynamic t [v]
207−distributeListOverDynPure =
208− fmap (map fromDSum . DMap.toAscList) .
209− distributeDMapOverDynPure .
210− DMap.fromDistinctAscList .
211− zipWith toDSum [0..]
212− where
213− toDSum :: Int -> Dynamic t a -> DSum (Const2 Int a) (Dynamic t)
214− toDSum k v = Const2 k :=> v
215− fromDSum :: DSum (Const2 Int a) Identity -> a
216− fromDSum (Const2 _ :=> Identity v) = v
208+distributeListOverDynPure = distributeListOverDyn
217209
218210--TODO: Generalize this to functors other than Maps
219211-- | Combine a 'Dynamic' of a 'Map' of 'Dynamic's into a 'Dynamic'
220212-- with the current values of the 'Dynamic's in a map.
221213joinDynThroughMap :: forall t k a. (Reflex t, Ord k) => Dynamic t (Map k (Dynamic t a)) -> Dynamic t (Map k a)
222−joinDynThroughMap = join . fmap distributeMapOverDynPure
214+joinDynThroughMap = (distributeMapOverDynPure =<<)
223215
224216-- | Print the value of the 'Dynamic' when it is first read and on each
225217-- subsequent change that is observed (as 'traceEvent'), prefixed with the
@@ -301,6 +293,8 @@ maybeDyn = fmap (fmap unpack) . eitherDyn . fmap pack
301293 Left _ -> Nothing
302294 Right a -> Just a
303295
296+-- | Turns a 'Dynamic t (Either a b)' into a 'Dynamic t (Either (Dynamic t a) (Dynamic t b))' such that
297+-- the outer 'Dynamic' is updated only when the 'Either' constructor changes (e.g., from 'Left' to 'Right').
304298eitherDyn :: forall t a b m. (Reflex t, MonadFix m, MonadHold t m) => Dynamic t (Either a b) -> m (Dynamic t (Either (Dynamic t a) (Dynamic t b)))
305299eitherDyn = fmap (fmap unpack) . factorDyn . fmap eitherToDSum
306300 where unpack :: DSum (EitherTag a b) (Compose (Dynamic t) Identity) -> Either (Dynamic t a) (Dynamic t b)
@@ -308,6 +302,9 @@ eitherDyn = fmap (fmap unpack) . factorDyn . fmap eitherToDSum
308302 LeftTag :=> Compose a -> Left $ coerceDynamic a
309303 RightTag :=> Compose b -> Right $ coerceDynamic b
310304
305+-- | Factor a 'Dynamic t DSum' into a 'Dynamic' 'DSum' containing nested 'Dynamic' values.
306+-- The outer 'Dynamic' updates only when the key of the 'DSum' changes, while the update of the inner
307+-- 'Dynamic' represents updates within the current key.
311308factorDyn :: forall t m k v. (Reflex t, MonadHold t m, GEq k)
312309 => Dynamic t (DSum k v) -> m (Dynamic t (DSum k (Compose (Dynamic t) v)))
313310factorDyn d = buildDynamic (sample (current d) >>= holdKey) update where
@@ -322,6 +319,7 @@ factorDyn d = buildDynamic (sample (current d) >>= holdKey) update where
322319 inner' <- filterEventKey k (updated d)
323320 inner <- holdDyn v inner'
324321 return $ k :=> Compose inner
322+
325323--------------------------------------------------------------------------------
326324-- Demux
327325--------------------------------------------------------------------------------
src/Reflex/DynamicWriter/Base.hs+2−2
@@ -88,7 +88,7 @@ mergeDynIncrementalWithMove a = unsafeBuildIncremental (mapM (sample . current)
8888 noLongerMovedMap = Map.fromList $ fmap (, ()) noLongerMoved
8989 in Map.differenceWith (\e _ -> Just $ MapWithMove.nodeInfoSetTo Nothing e) pWithNewVals noLongerMovedMap --TODO: Check if any in the second map are not covered?
9090
91−-- | A basic implementation of 'MonadDynamicWriter'.
91+-- | A basic implementation of 'DynamicWriter'.
9292newtype DynamicWriterT t w m a = DynamicWriterT { unDynamicWriterT :: StateT [Dynamic t w] m a }
9393 deriving (Functor, Applicative, Monad, MonadIO, MonadFix, MonadAsyncException, MonadException) -- The list is kept in reverse order
9494
@@ -116,7 +116,7 @@ runDynamicWriterT (DynamicWriterT a) = do
116116 (result, ws) <- runStateT a []
117117 return (result, mconcat $ reverse ws)
118118
119−instance (Monad m, Monoid w, Reflex t) => MonadDynamicWriter t w (DynamicWriterT t w m) where
119+instance (Monad m, Monoid w, Reflex t) => DynamicWriter t w (DynamicWriterT t w m) where
120120 tellDyn w = DynamicWriterT $ modify (w :)
121121
122122instance MonadReader r m => MonadReader r (DynamicWriterT t w m) where
src/Reflex/DynamicWriter/Class.hs+10−4
@@ -1,4 +1,5 @@
1−-- | This module defines the 'MonadDynamicWriter' class.
1+-- | This module defines the 'DynamicWriter' class.
2+{-# LANGUAGE ConstraintKinds #-}
23{-# LANGUAGE CPP #-}
34{-# LANGUAGE FlexibleInstances #-}
45{-# LANGUAGE FunctionalDependencies #-}
@@ -8,17 +9,22 @@
89{-# OPTIONS_GHC -fplugin=Reflex.Optimizer #-}
910#endif
1011module Reflex.DynamicWriter.Class
11− ( MonadDynamicWriter (..)
12+ ( MonadDynamicWriter
13+ , DynamicWriter(..)
1214 ) where
1315
1416import Control.Monad.Reader (ReaderT, lift)
1517import Reflex.Class (Dynamic)
1618
19+{-# DEPRECATED MonadDynamicWriter "Use 'DynamicWriter' instead" #-}
20+-- | Type synonym for 'DynamicWriter'
21+type MonadDynamicWriter = DynamicWriter
22+
1723-- | 'MonadDynamicWriter' efficiently collects 'Dynamic' values using 'tellDyn'
1824-- and combines them monoidally to provide a 'Dynamic' result.
19−class (Monad m, Monoid w) => MonadDynamicWriter t w m | m -> t w where
25+class (Monad m, Monoid w) => DynamicWriter t w m | m -> t w where
2026 tellDyn :: Dynamic t w -> m ()
2127
22−instance MonadDynamicWriter t w m => MonadDynamicWriter t w (ReaderT r m) where
28+instance DynamicWriter t w m => DynamicWriter t w (ReaderT r m) where
2329 tellDyn = lift . tellDyn
2430
src/Reflex/EventWriter/Base.hs+2−2
@@ -25,7 +25,7 @@ module Reflex.EventWriter.Base
2525import Reflex.Adjustable.Class
2626import Reflex.Class
2727import Reflex.EventWriter.Class (EventWriter, tellEvent)
28−import Reflex.DynamicWriter.Class (MonadDynamicWriter, tellDyn)
28+import Reflex.DynamicWriter.Class (DynamicWriter, tellDyn)
2929import Reflex.Host.Class
3030import Reflex.PerformEvent.Class
3131import Reflex.PostBuild.Class
@@ -278,7 +278,7 @@ instance (MonadQuery t q m, Monad m) => MonadQuery t q (EventWriterT t w m) wher
278278 askQueryResult = lift askQueryResult
279279 queryIncremental = lift . queryIncremental
280280
281−instance MonadDynamicWriter t w m => MonadDynamicWriter t w (EventWriterT t v m) where
281+instance DynamicWriter t w m => DynamicWriter t w (EventWriterT t v m) where
282282 tellDyn = lift . tellDyn
283283
284284instance PrimMonad m => PrimMonad (EventWriterT t w m) where
src/Reflex/FunctorMaybe.hs+2
@@ -1,6 +1,8 @@
11{-# LANGUAGE CPP #-}
2+#if MIN_VERSION_base(4,9,0)
23{-# LANGUAGE GeneralizedNewtypeDeriving #-}
34{-# LANGUAGE StandaloneDeriving #-}
5+#endif
46
57-- |
68-- Module:
src/Reflex/Query/Base.hs+23−30
@@ -74,26 +74,19 @@ getQueryTLoweredResultValue (QueryTLoweredResult (v, _)) = v
7474getQueryTLoweredResultWritten :: QueryTLoweredResult t q v -> [Behavior t q]
7575getQueryTLoweredResultWritten (QueryTLoweredResult (_, w)) = w
7676
77−{-
78−let sampleBs :: forall m'. MonadSample t m' => [Behavior t q] -> m' q
79− sampleBs = foldlM (\b a -> (b <>) <$> sample a) mempty
80− bs' = fmapCheap snd $ r'
81− patches = unsafeBuildIncremental (sampleBs bs0) $
82− flip pushCheap bs' $ \bs -> do
83− p <- (~~) <$> sampleBs bs <*> sample (currentIncremental patches)
84− return (Just (AdditivePatch p))
85−-}
77+maskMempty :: (Eq a, Monoid a) => a -> Maybe a
78+maskMempty x = if x == mempty then Nothing else Just x
8679
87−instance (Reflex t, MonadFix m, Group q, Additive q, Query q, MonadHold t m, Adjustable t m) => Adjustable t (QueryT t q m) where
80+instance (Reflex t, MonadFix m, Group q, Additive q, Query q, Eq q, MonadHold t m, Adjustable t m) => Adjustable t (QueryT t q m) where
8881 runWithReplace (QueryT a0) a' = do
8982 ((r0, bs0), r') <- QueryT $ lift $ runWithReplace (runStateT a0 []) $ fmapCheap (flip runStateT [] . unQueryT) a'
9083 let sampleBs :: forall m'. MonadSample t m' => [Behavior t q] -> m' q
9184 sampleBs = foldlM (\b a -> (b <>) <$> sample a) mempty
9285 bs' = fmapCheap snd $ r'
9386 bbs <- hold bs0 bs'
94− let patches = flip pushAlwaysCheap bs' $ \newBs -> do
87+ let patches = flip pushCheap bs' $ \newBs -> do
9588 oldBs <- sample bbs
96− (~~) <$> sampleBs newBs <*> sampleBs oldBs
89+ maskMempty <$> ((~~) <$> sampleBs newBs <*> sampleBs oldBs)
9790 QueryT $ modify $ (:) $ pull $ sampleBs =<< sample bbs
9891 QueryT $ lift $ tellEvent patches
9992 return (r0, fmapCheap fst r')
@@ -171,19 +164,19 @@ instance (Reflex t, MonadFix m, Group q, Additive q, Query q, MonadHold t m, Adj
171164 let p k bs = case Map.lookup k bs0 of
172165 Nothing -> case bs of
173166 -- If the update is to delete the state for a child that doesn't exist, the patch is mempty.
174− Nothing -> return mempty
167+ Nothing -> return Nothing
175168 -- If the update is to update the state for a child that doesn't exist, the patch is the sample of the new state.
176− Just newBs -> sampleBs newBs
169+ Just newBs -> maskMempty <$> sampleBs newBs
177170 Just oldBs -> case bs of
178171 -- If the update is to delete the state for a child that already exists, the patch is the negation of the child's current state
179− Nothing -> negateG <$> sampleBs oldBs
172+ Nothing -> maskMempty . negateG <$> sampleBs oldBs
180173 -- If the update is to update the state for a child that already exists, the patch is the negation of sampling the child's current state
181174 -- composed with the sampling the child's new state.
182− Just newBs -> (~~) <$> sampleBs newBs <*> sampleBs oldBs
175+ Just newBs -> maskMempty <$> ((~~) <$> sampleBs newBs <*> sampleBs oldBs)
183176 -- we compute the patch by iterating over the update PatchMap and proceeding by cases. Then we fold over the
184177 -- child patches and wrap them in AdditivePatch.
185− patch <- AdditivePatch . fold <$> Map.traverseWithKey p bs'
186− return (apply pbs bs0, Just patch)
178+ patch <- fold <$> Map.traverseWithKey p bs'
179+ return (apply pbs bs0, AdditivePatch <$> patch)
187180 (qpatch :: Event t (AdditivePatch q)) <- mapAccumMaybeM_ accumBehaviors liftedBs0 liftedBs'
188181 tellQueryIncremental $ unsafeBuildIncremental (fold <$> mapM sampleBs liftedBs0) qpatch
189182 return (liftedResult0, liftedResult')
@@ -216,28 +209,28 @@ instance (Reflex t, MonadFix m, Group q, Additive q, Query q, MonadHold t m, Adj
216209 p k bs = case Map.lookup k bs0 of
217210 Nothing -> case MapWithMove._nodeInfo_from bs of
218211 -- If the update is to delete the state for a child that doesn't exist, the patch is mempty.
219− MapWithMove.From_Delete -> return mempty
212+ MapWithMove.From_Delete -> return Nothing
220213 -- If the update is to update the state for a child that doesn't exist, the patch is the sample of the new state.
221− MapWithMove.From_Insert newBs -> sampleBs newBs
214+ MapWithMove.From_Insert newBs -> maskMempty <$> sampleBs newBs
222215 MapWithMove.From_Move k' -> case Map.lookup k' bs0 of
223− Nothing -> return mempty
224− Just newBs -> sampleBs newBs
216+ Nothing -> return Nothing
217+ Just newBs -> maskMempty <$> sampleBs newBs
225218 Just oldBs -> case MapWithMove._nodeInfo_from bs of
226219 -- If the update is to delete the state for a child that already exists, the patch is the negation of the child's current state
227− MapWithMove.From_Delete -> negateG <$> sampleBs oldBs
220+ MapWithMove.From_Delete -> maskMempty . negateG <$> sampleBs oldBs
228221 -- If the update is to update the state for a child that already exists, the patch is the negation of sampling the child's current state
229222 -- composed with the sampling the child's new state.
230− MapWithMove.From_Insert newBs -> (~~) <$> sampleBs newBs <*> sampleBs oldBs
223+ MapWithMove.From_Insert newBs -> maskMempty <$> ((~~) <$> sampleBs newBs <*> sampleBs oldBs)
231224 MapWithMove.From_Move k'
232− | k' == k -> return mempty
225+ | k' == k -> return Nothing
233226 | otherwise -> case Map.lookup k' bs0 of
234227 -- If we are moving from a non-existent key, that is a delete
235− Nothing -> negateG <$> sampleBs oldBs
236− Just newBs -> (~~) <$> sampleBs newBs <*> sampleBs oldBs
228+ Nothing -> maskMempty . negateG <$> sampleBs oldBs
229+ Just newBs -> maskMempty <$> ((~~) <$> sampleBs newBs <*> sampleBs oldBs)
237230 -- we compute the patch by iterating over the update PatchMap and proceeding by cases. Then we fold over the
238231 -- child patches and wrap them in AdditivePatch.
239− patch <- AdditivePatch . fold <$> Map.traverseWithKey p bs'
240− return (apply pbs bs0, Just patch)
232+ patch <- fold <$> Map.traverseWithKey p bs'
233+ return (apply pbs bs0, AdditivePatch <$> patch)
241234 (qpatch :: Event t (AdditivePatch q)) <- mapAccumMaybeM_ accumBehaviors' liftedBs0 liftedBs'
242235 tellQueryIncremental $ unsafeBuildIncremental (fold <$> mapM sampleBs liftedBs0) qpatch
243236 return (liftedResult0, liftedResult')
@@ -340,5 +333,5 @@ instance Requester t m => Requester t (QueryT t q m) where
340333instance EventWriter t w m => EventWriter t w (QueryT t q m) where
341334 tellEvent = lift . tellEvent
342335
343−instance MonadDynamicWriter t w m => MonadDynamicWriter t w (QueryT t q m) where
336+instance DynamicWriter t w m => DynamicWriter t w (QueryT t q m) where
344337 tellDyn = lift . tellDyn
src/Reflex/Query/Class.hs+24−4
@@ -6,6 +6,13 @@
66{-# LANGUAGE MultiParamTypeClasses #-}
77{-# LANGUAGE TypeFamilies #-}
88{-# LANGUAGE UndecidableInstances #-}
9+-- |
10+-- Module:
11+-- Reflex.Query.Class
12+-- Description:
13+-- A class that ties together queries to some data source and their results,
14+-- providing methods for requesting data from the source and accumulating
15+-- streamed results.
916module Reflex.Query.Class
1017 ( Query (..)
1118 , QueryMorphism (..)
@@ -32,6 +39,10 @@ import Foreign.Storable
3239
3340import Reflex.Class
3441
42+-- | A 'Query' can be thought of as a declaration of interest in some set of data.
43+-- A 'QueryResult' is the set of data associated with that interest set.
44+-- The @crop@ function provides a way to determine what part of a given 'QueryResult'
45+-- is relevant to a given 'Query'.
3546class (Monoid (QueryResult a), Semigroup (QueryResult a)) => Query a where
3647 type QueryResult a :: *
3748 crop :: a -> QueryResult a -> QueryResult a
@@ -40,8 +51,8 @@ instance (Ord k, Query v) => Query (MonoidalMap k v) where
4051 type QueryResult (MonoidalMap k v) = MonoidalMap k (QueryResult v)
4152 crop q r = MonoidalMap.intersectionWith (flip crop) r q
4253
43−-- | NB: QueryMorphism's must be group homomorphisms when acting on the query type
44−-- and compatible with the query relationship when acting on the query result
54+-- | QueryMorphism's must be group homomorphisms when acting on the query type
55+-- and compatible with the query relationship when acting on the query result.
4556data QueryMorphism q q' = QueryMorphism
4657 { _queryMorphism_mapQuery :: q -> q'
4758 , _queryMorphism_mapQueryResult :: QueryResult q' -> QueryResult q
@@ -54,13 +65,16 @@ instance Category QueryMorphism where
5465 , _queryMorphism_mapQueryResult = mapQueryResult qm' . mapQueryResult qm
5566 }
5667
68+-- | Apply a 'QueryMorphism' to a 'Query'
5769mapQuery :: QueryMorphism q q' -> q -> q'
5870mapQuery = _queryMorphism_mapQuery
5971
72+-- | Map a 'QueryMorphism' to a 'QueryResult'
6073mapQueryResult :: QueryMorphism q q' -> QueryResult q' -> QueryResult q
6174mapQueryResult = _queryMorphism_mapQueryResult
6275
63−-- | This type keeps track of the multiplicity of elements of the view selector that are being used by the app
76+-- | This type can be used to track of the frequency of interest in a given 'Query'. See note on
77+-- 'combineSelectedCounts'
6478newtype SelectedCount = SelectedCount { unSelectedCount :: Int }
6579 deriving (Eq, Ord, Show, Read, Integral, Num, Bounded, Enum, Real, Ix, Bits, FiniteBits, Storable, Data)
6680
@@ -76,10 +90,14 @@ instance Group SelectedCount where
7690
7791instance Additive SelectedCount
7892
79−-- | The Semigroup/Monoid/Group instances for a ViewSelector should use this function which returns Nothing if the result is 0. This allows the pruning of leaves that are no longer wanted.
93+-- | The Semigroup/Monoid/Group instances for a Query containing 'SelectedCount's should use
94+-- this function which returns Nothing if the result is 0. This allows the pruning of leaves
95+-- of the 'Query' that are no longer wanted.
8096combineSelectedCounts :: SelectedCount -> SelectedCount -> Maybe SelectedCount
8197combineSelectedCounts (SelectedCount i) (SelectedCount j) = if i == negate j then Nothing else Just $ SelectedCount (i + j)
8298
99+-- | A class that allows sending of 'Query's and retrieval of 'QueryResult's. See 'queryDyn' for a commonly
100+-- used interface.
83101class (Group q, Additive q, Query q) => MonadQuery t q m | m -> q t where
84102 tellQueryIncremental :: Incremental t (AdditivePatch q) -> m ()
85103 askQueryResult :: m (Dynamic t (QueryResult q))
@@ -90,9 +108,11 @@ instance (Monad m, MonadQuery t q m) => MonadQuery t q (ReaderT r m) where
90108 askQueryResult = lift askQueryResult
91109 queryIncremental = lift . queryIncremental
92110
111+-- | Produce and send an 'Incremental' 'Query' from a 'Dynamic' 'Query'.
93112tellQueryDyn :: (Reflex t, MonadQuery t q m) => Dynamic t q -> m ()
94113tellQueryDyn d = tellQueryIncremental $ unsafeBuildIncremental (sample (current d)) $ attachWith (\old new -> AdditivePatch $ new ~~ old) (current d) (updated d)
95114
115+-- | Retrieve 'Dynamic'ally updating 'QueryResult's for a 'Dynamic'ally updating 'Query'.
96116queryDyn :: (Reflex t, Monad m, MonadQuery t q m) => Dynamic t q -> m (Dynamic t (QueryResult q))
97117queryDyn q = do
98118 tellQueryDyn q
src/Reflex/Spider/Internal.hs+4−1
@@ -705,7 +705,7 @@ readBehaviorUntracked b = do
705705
706706data Dynamic x p = Dynamic
707707 { dynamicCurrent :: !(Behavior x (PatchTarget p))
708− , dynamicUpdated :: !(Event x p)
708+ , dynamicUpdated :: Event x p -- This must be lazy; see the comment on holdEvent --TODO: Would this let us eliminate `Dyn`?
709709 }
710710
711711dynamicHold :: Hold x p -> Dynamic x p
@@ -1133,6 +1133,9 @@ instance HasSpiderTimeline x => Filterable (Event x) where
11331133
11341134instance HasSpiderTimeline x => Align (Event x) where
11351135 nil = eventNever
1136+#if MIN_VERSION_these(0, 8, 0)
1137+instance HasSpiderTimeline x => Semialign (Event x) where
1138+#endif
11361139 align ea eb = mapMaybe dmapToThese $ merge $ dynamicConst $ DMap.fromDistinctAscList [LeftTag :=> ea, RightTag :=> eb]
11371140
11381141data DynType x p = UnsafeDyn !(BehaviorM x (PatchTarget p), Event x p)
test/EventWriterT.hs+18
@@ -1,3 +1,4 @@
1+{-# LANGUAGE BangPatterns #-}
12{-# LANGUAGE FlexibleContexts #-}
23{-# LANGUAGE GADTs #-}
34{-# LANGUAGE RecursiveDo #-}
@@ -36,6 +37,9 @@ main = do
3637 print os4
3738 os5@[[Nothing, Just [1, 2]]] <- runApp' (unwrapApp testLiveTellEventDMap) [Just ()]
3839 print os5
40+ os6 <- runApp' (unwrapApp delayedPulse) [Just ()]
41+ print os6
42+ let ![[Nothing, Nothing]] = os6
3943 return ()
4044
4145unwrapApp :: (Reflex t, Monad m) => (a -> EventWriterT t [Int] m ()) -> a -> m (Event t [Int])
@@ -112,3 +116,17 @@ testLiveTellEventDMap pulse = do
112116 (mapToDMap $ M.singleton 1 ())
113117 ((PatchDMap $ DMap.map (ComposeMaybe . Just) $ mapToDMap $ M.singleton 2 ()) <$ pulse)
114118 return ()
119+
120+delayedPulse
121+ :: forall t m
122+ . ( Reflex t
123+ , Adjustable t m
124+ , MonadHold t m
125+ , MonadFix m
126+ )
127+ => Event t ()
128+ -> EventWriterT t [Int] m ()
129+delayedPulse pulse = void $ flip runWithReplace (pure () <$ pulse) $ do
130+ -- This has the effect of delaying pulse' from pulse
131+ (_, pulse') <- runWithReplace (pure ()) $ pure [1] <$ pulse
132+ tellEvent pulse'
test/RequesterT.hs+111−9
@@ -1,13 +1,18 @@
1+{-# LANGUAGE BangPatterns #-}
12{-# LANGUAGE FlexibleContexts #-}
23{-# LANGUAGE GADTs #-}
34{-# LANGUAGE RankNTypes #-}
5+{-# LANGUAGE RecursiveDo #-}
46{-# LANGUAGE ScopedTypeVariables #-}
57module Main where
68
79import Control.Lens
810import Control.Monad
11+import Control.Monad.Fix
912import qualified Data.Dependent.Map as DMap
1013import Data.Dependent.Sum
14+import Data.Functor.Misc
15+import qualified Data.Map as M
1116import Data.These
1217
1318import Reflex
@@ -20,18 +25,31 @@ data RequestInt a where
2025
2126main :: IO ()
2227main = do
23− os1@[[Just [10,9,8,7,6,5,4,3,2,1]]] <- runApp' (unwrapApp testOrdering) $
28+ os1 <- runApp' (unwrapApp testOrdering) $
2429 [ Just ()
2530 ]
2631 print os1
27− os2@[[Just [1,3,5,7,9]],[Nothing,Nothing],[Just [2,4,6,8,10]],[Just [2,4,6,8,10],Nothing]]
28− <- runApp' (unwrapApp testSimultaneous) $ map Just $
29− [ This ()
30− , That ()
31− , This ()
32− , These () ()
33− ]
32+ os2 <- runApp' (unwrapApp testSimultaneous) $ map Just $
33+ [ This ()
34+ , That ()
35+ , This ()
36+ , These () ()
37+ ]
3438 print os2
39+ os3 <- runApp' (unwrapApp testMoribundRequest) [Just ()]
40+ print os3
41+ os4 <- runApp' (unwrapApp testMoribundRequestDMap) [Just ()]
42+ print os4
43+ os5 <- runApp' (unwrapApp testLiveRequestDMap) [Just ()]
44+ print os5
45+ os6 <- runApp' (unwrapApp delayedPulse) [Just ()]
46+ print os6
47+ let ![[Just [1,2,3,4,5,6,7,8,9,10]]] = os1 -- The order is reversed here: see the documentation for 'runRequesterT'
48+ let ![[Just [9,7,5,3,1]],[Nothing,Nothing],[Just [10,8,6,4,2]],[Just [10,8,6,4,2],Nothing]] = os2
49+ let ![[Nothing, Just [2]]] = os3
50+ let ![[Nothing, Just [2]]] = os4
51+ let ![[Nothing, Just [1, 2]]] = os5
52+ -- let ![[Nothing, Nothing]] = os6 -- TODO re-enable this test after issue #233 has been resolved
3553 return ()
3654
3755unwrapRequest :: DSum tag RequestInt -> Int
@@ -43,7 +61,7 @@ unwrapApp :: ( Reflex t, Monad m )
4361 -> m (Event t [Int])
4462unwrapApp x appIn = do
4563 ((), e) <- runRequesterT (x appIn) never
46− return $ fmap (map unwrapRequest . DMap.toList) e
64+ return $ fmap (map unwrapRequest . requesterDataToList) e
4765
4866testOrdering :: ( Response m ~ Identity
4967 , Request m ~ RequestInt
@@ -65,3 +83,87 @@ testSimultaneous pulse = do
6583 switchE = fmapMaybe (^? there) pulse
6684 forM_ [1,3..9] $ \i -> runWithReplace (requestingIdentity (RequestInt i <$ tellE)) $ ffor switchE $ \_ ->
6785 requestingIdentity (RequestInt (i+1) <$ tellE)
86+
87+-- | Test that a widget requesting and event which fires at the same time it has been replaced
88+-- doesn't count along with the new widget.
89+testMoribundRequest
90+ :: forall t m
91+ . ( Reflex t
92+ , Adjustable t m
93+ , MonadHold t m
94+ , MonadFix m
95+ , Response m ~ Identity
96+ , Request m ~ RequestInt
97+ , Requester t m
98+ )
99+ => Event t ()
100+ -> m ()
101+testMoribundRequest pulse = do
102+ rec let requestIntOnReplace x = requestingIdentity $ RequestInt x <$ rwrFinished
103+ (_, rwrFinished) <- runWithReplace (requestIntOnReplace 1) $ requestIntOnReplace 2 <$ pulse
104+ return ()
105+
106+-- | The equivalent of 'testMoribundRequest' for 'traverseDMapWithKeyWithAdjust'.
107+testMoribundRequestDMap
108+ :: forall t m
109+ . ( Reflex t
110+ , Adjustable t m
111+ , MonadHold t m
112+ , MonadFix m
113+ , Response m ~ Identity
114+ , Request m ~ RequestInt
115+ , Requester t m
116+ )
117+ => Event t ()
118+ -> m ()
119+testMoribundRequestDMap pulse = do
120+ rec let requestIntOnReplace :: Int -> m ()
121+ requestIntOnReplace x = void $ requestingIdentity $ RequestInt x <$ rwrFinished
122+ (_, rwrFinished :: Event t (PatchDMap (Const2 () Int) Identity)) <-
123+ traverseDMapWithKeyWithAdjust
124+ (\(Const2 ()) (Identity v) -> Identity . const v <$> requestIntOnReplace v)
125+ (mapToDMap $ M.singleton () 1)
126+ ((PatchDMap $ DMap.map (ComposeMaybe . Just) $ mapToDMap $ M.singleton () 2) <$ pulse)
127+ return ()
128+
129+-- | Ensures that elements which are _not_ removed can still fire requests
130+-- during the same frame as other elements are updated.
131+testLiveRequestDMap
132+ :: forall t m
133+ . ( Reflex t
134+ , Adjustable t m
135+ , MonadHold t m
136+ , MonadFix m
137+ , Response m ~ Identity
138+ , Request m ~ RequestInt
139+ , Requester t m
140+ )
141+ => Event t ()
142+ -> m ()
143+testLiveRequestDMap pulse = do
144+ rec let requestIntOnReplace :: Int -> m ()
145+ requestIntOnReplace x = void $ requestingIdentity $ RequestInt x <$ rwrFinished
146+ (_, rwrFinished :: Event t (PatchDMap (Const2 Int ()) Identity)) <-
147+ traverseDMapWithKeyWithAdjust
148+ (\(Const2 k) (Identity ()) -> Identity <$> requestIntOnReplace k)
149+ (mapToDMap $ M.singleton 1 ())
150+ ((PatchDMap $ DMap.map (ComposeMaybe . Just) $ mapToDMap $ M.singleton 2 ()) <$ pulse)
151+ return ()
152+
153+delayedPulse
154+ :: forall t m
155+ . ( Reflex t
156+ , Adjustable t m
157+ , MonadHold t m
158+ , MonadFix m
159+ , Response m ~ Identity
160+ , Request m ~ RequestInt
161+ , PerformEvent t m
162+ , Requester t m
163+ )
164+ => Event t ()
165+ -> m ()
166+delayedPulse pulse = void $ flip runWithReplace (pure () <$ pulse) $ do
167+ -- This has the effect of delaying pulse' from pulse
168+ (_, pulse') <- runWithReplace (pure ()) $ pure (RequestInt 1) <$ pulse
169+ requestingIdentity pulse'