on develop help sign in
21 files changed+300−81
since splitsnapshots
unifiedsplit
ChangeLog.md+26 Quickref.md+1 README.md+14−22 reflex.cabal+29−8 src/Reflex/BehaviorWriter/Base.hs+13−4 src/Reflex/Class.hs+13 src/Reflex/Dynamic.hs+1−1 src/Reflex/DynamicWriter/Base.hs+13−4 src/Reflex/EventWriter/Base.hs+14−4 src/Reflex/Host/Headless.hs+11−9 src/Reflex/PerformEvent/Base.hs+2 src/Reflex/PostBuild/Base.hs+2 src/Reflex/Profiled.hs+1 src/Reflex/Pure.hs+4 src/Reflex/Query/Base.hs+4 src/Reflex/Requester/Base.hs+95−26 src/Reflex/Spider/Internal.hs+36−1 src/Reflex/TriggerEvent/Base.hs+2 test/Headless.hs+10 test/Reflex/Plan/Pure.hs+1 test/Reflex/Test/Micro.hs+8−2
ChangeLog.md+26
@@ -1,7 +1,33 @@
11# Revision history for reflex
22
3+## 0.8.2.0
4+
5+* Add `matchResponseMapWithRequests`, which it similar to `matchResponsesWithRequests` but allows processing of multiple responses at once.
6+
7+## 0.8.1.1
8+
9+* Allow newer hlint for older GHCs, and add upper bound for newer GHCs
10+
11+## 0.8.1.0
12+
13+* Add support for GHC 8.10
14+* Drop support for GHC < 8.4
15+
16+## 0.8.0.0
17+
18+* Replace 0.7.2.0 with 0.8.0.0 to reflect the `MonadHold` interface change. Deprecates 0.7.2.0.
19+
20+## 0.7.2.0 -- *Deprecated*
21+
22+* ([#416](https://github.com/reflex-frp/reflex/pull/416)) Add `now :: m (Event t ())` to `MonadHold`.
23+* Extend some dependency version bounds
24+* Fix HLint 3 test
25+
326## 0.7.1.1
427
28+*Backport release*.
29+Changes do not carry forward to 0.7.2.0.
30+
531* Add support for GHC 8.10
632* Drop support for GHC < 8.4
733* Extend some dependency version bounds
Quickref.md+1
@@ -54,6 +54,7 @@ Since MonadHold depends on MonadSample, any [S] function also runs in [H] contex
5454[ ] difference :: Event a -> Event b -> Event a
5555[ ] align :: Event a -> Event b -> Event (These a b)
5656[ ] alignWith :: (These a b -> c) -> Event a -> Event b -> Event c
57+ -- Note align functions from Data.Align in semialign package
5758[ ] mergeWith :: (a -> a -> a) -> [Event a] -> Event a
5859[ ] leftmost :: [Event a] -> Event a
5960[ ] mergeList :: [Event a] -> Event (NonEmpty a)
README.md+14−22
@@ -1,31 +1,23 @@
1−## [Reflex](https://reflex-frp.org/)
1+# [Reflex](https://reflex-frp.org/)
22
33[![Haskell](https://img.shields.io/badge/language-Haskell-orange.svg)](https://haskell.org) [![Hackage](https://img.shields.io/hackage/v/reflex.svg)](https://hackage.haskell.org/package/reflex) [![Hackage CI](https://matrix.hackage.haskell.org/api/v2/packages/reflex/badge)](https://matrix.hackage.haskell.org/#/package/reflex) [![BSD3 License](https://img.shields.io/badge/license-BSD3-blue.svg)](https://github.com/reflex-frp/reflex/blob/master/LICENSE)
44
5−### Practical Functional Reactive Programming
5+Interactive programs without callbacks or side-effects. Functional Reactive Programming (FRP) uses composable events and time-varying values to describe interactive systems as pure functions. Just like other pure functional code, functional reactive code is easier to get right on the first try, maintain, and reuse.
66
7−Reflex is a fully-deterministic, higher-order Functional Reactive Programming (FRP) interface and an engine that efficiently implements that interface.
7+Reflex is a fully-deterministic, higher-order Functional Reactive Programming interface and an engine that efficiently implements that interface.
88
9−[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.
9+**Visit https://reflex-frp.org for more information, tutorials, documentation and [examples](https://examples.reflex-frp.org/).**
1010
11−A summary of Reflex functions is available in the [quick reference](Quickref.md).
11+## Resources
1212
13−**Visit https://reflex-frp.org/ for more information, tutorials, documentation and [examples](https://examples.reflex-frp.org/).**
13+* [Official Website](https://reflex-frp.org)
14+* [Quick Reference](Quickref.md)
15+* [Reflex-DOM](https://github.com/reflex-frp/reflex-dom): A framework built on Reflex that facilitates the development of web pages, including highly-interactive single-page apps.
16+* [Obelisk](https://github.com/obsidiansystems/obelisk#obelisk): A framework built on Reflex and Reflex-DOM for functional reactive web and mobile applications, with batteries included.
17+* [Get started with Reflex](https://github.com/reflex-frp/reflex-platform)
18+* [/r/reflexfrp](https://www.reddit.com/r/reflexfrp)
19+* [irc.freenode.net #reflex-frp](http://webchat.freenode.net?channels=%23reflex-frp&uio=d4)
1420
15−### Resources
16−[Get started with Reflex](https://github.com/reflex-frp/reflex-platform)
21+## Hacking
1722
18−[/r/reflexfrp](https://www.reddit.com/r/reflexfrp)
19−
20−[irc.freenode.net #reflex-frp](http://webchat.freenode.net?channels=%23reflex-frp&uio=d4)
21−
22−### Hacking
23−
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.
23+From the root of a [Reflex Platform](https://github.com/reflex-frp/reflex-platform) checkout, run `./scripts/hack-on haskell-overlays/reflex-packages/dep/reflex`. This will check out the reflex source code into the `haskell-overlays/reflex-packages/dep/reflex` directory. You can then point that checkout at your fork, make changes, etc. Use the `./try-reflex` or `./scripts/work-on` scripts to start a shell in which you can test your changes.
reflex.cabal+29−8
@@ -1,7 +1,17 @@
11Name: reflex
2−Version: 0.7.1.1
2+Version: 0.8.2.0
33Synopsis: Higher-order Functional Reactive Programming
4−Description: Reflex is a high-performance, deterministic, higher-order Functional Reactive Programming system
4+Description:
5+ Interactive programs without callbacks or side-effects.
6+ Functional Reactive Programming (FRP) uses composable events and time-varying
7+ values to describe interactive systems as pure functions.
8+ Just like other pure functional code, functional reactive code is easier
9+ to get right on the first try, maintain, and reuse.
10+ .
11+ Reflex is a fully-deterministic, higher-order Functional Reactive Programming
12+ interface and an engine that efficiently implements that interface.
13+ .
14+ <https://reflex-frp.org>
515License: BSD3
616License-file: LICENSE
717Author: Ryan Trinkle
@@ -64,6 +74,7 @@ library
6474 base >= 4.11 && < 4.15,
6575 bifunctors >= 5.2 && < 5.6,
6676 comonad >= 5.0.4 && < 5.1,
77+ constraints >= 0.10 && <0.14,
6778 constraints-extras >= 0.3 && < 0.4,
6879 containers >= 0.6 && < 0.7,
6980 data-default >= 0.5 && < 0.8,
@@ -86,7 +97,7 @@ library
8697 time >= 1.4 && < 1.10,
8798 transformers >= 0.5.6.0 && < 0.6,
8899 unbounded-delays >= 0.1.0.0 && < 0.2,
89− witherable >= 0.3 && < 0.4
100+ witherable >= 0.3 && < 0.5
90101
91102 if flag(split-these)
92103 build-depends: these >= 1 && <1.2,
@@ -155,7 +166,8 @@ library
155166
156167 if flag(debug-trace-events)
157168 cpp-options: -DDEBUG_TRACE_EVENTS
158− build-depends: bytestring
169+ build-depends:
170+ bytestring >= 0.10.8 && < 0.11
159171
160172 if flag(use-reflex-optimizer)
161173 cpp-options: -DUSE_REFLEX_OPTIMIZER
@@ -180,13 +192,14 @@ library
180192 other-extensions: TemplateHaskell
181193 else
182194 build-depends:
183− dependent-sum >= 0.6 && < 0.7
195+ dependent-sum >= 0.6 && < 0.8
184196
185197 if flag(fast-weak) && impl(ghcjs)
186198 cpp-options: -DGHCJS_FAST_WEAK
187199
188200 if impl(ghcjs)
189− build-depends: ghcjs-base
201+ build-depends:
202+ ghcjs-base == 0.2.*
190203
191204test-suite semantics
192205 default-language: Haskell2010
@@ -246,9 +259,9 @@ test-suite hlint
246259 , filepath
247260 , filemanip
248261 if impl(ghc >= 8.8)
249− build-depends: hlint >= 3
262+ build-depends: hlint >= 3 && < 4
250263 else
251− build-depends: hlint (< 2.1 || >= 2.2.2) && < 3
264+ build-depends: hlint (< 2.1 || >= 2.2.2) && < 4
252265 if impl(ghcjs)
253266 buildable: False
254267
@@ -333,6 +346,14 @@ test-suite RequesterT
333346 Reflex.Plan.Pure
334347 Test.Run
335348
349+test-suite Headless
350+ default-language: Haskell2010
351+ type: exitcode-stdio-1.0
352+ main-is: Headless.hs
353+ hs-source-dirs: test
354+ build-depends: base
355+ , reflex
356+
336357test-suite Adjustable
337358 default-language: Haskell2010
338359 type: exitcode-stdio-1.0
src/Reflex/BehaviorWriter/Base.hs+13−4
@@ -24,6 +24,7 @@ module Reflex.BehaviorWriter.Base
2424import Control.Monad.Exception
2525import Control.Monad.Identity
2626import Control.Monad.IO.Class
27+import Control.Monad.Morph
2728import Control.Monad.Reader
2829import Control.Monad.Ref
2930import Control.Monad.State.Strict
@@ -48,7 +49,18 @@ import Reflex.TriggerEvent.Class
4849
4950-- | A basic implementation of 'BehaviorWriter'.
5051newtype BehaviorWriterT t w m a = BehaviorWriterT { unBehaviorWriterT :: StateT [Behavior t w] m a }
51− deriving (Functor, Applicative, Monad, MonadIO, MonadFix, MonadAsyncException, MonadException) -- The list is kept in reverse order
52+ -- The list is kept in reverse order
53+ deriving
54+ ( Functor
55+ , Applicative
56+ , Monad
57+ , MonadTrans
58+ , MFunctor
59+ , MonadIO
60+ , MonadFix
61+ , MonadAsyncException
62+ , MonadException
63+ )
5264
5365-- | Run a 'BehaviorWriterT' action. The behavior writer output will be provided
5466-- along with the result of the action.
@@ -73,9 +85,6 @@ withBehaviorWriterT f dw = do
7385deriving instance MonadHold t m => MonadHold t (BehaviorWriterT t w m)
7486deriving instance MonadSample t m => MonadSample t (BehaviorWriterT t w m)
7587
76−instance MonadTrans (BehaviorWriterT t w) where
77− lift = BehaviorWriterT . lift
78−
7988instance MonadRef m => MonadRef (BehaviorWriterT t w m) where
8089 type Ref (BehaviorWriterT t w m) = Ref m
8190 newRef = lift . newRef
src/Reflex/Class.hs+13
@@ -421,6 +421,13 @@ class MonadSample t m => MonadHold t m where
421421 -- | Create a new 'Event' that only occurs only once, on the first occurrence of
422422 -- the supplied 'Event'.
423423 headE :: Event t a -> m (Event t a)
424+ -- | An event which only occurs at the current moment in time, such that:
425+ --
426+ -- > coincidence (pushAlways (\a -> (a <$) <$> now) e) = e
427+ --
428+ now :: m (Event t ())
429+ default now :: (m ~ f m', MonadTrans f, MonadHold t m') => m (Event t ())
430+ now = lift now
424431
425432-- | Accumulate an 'Incremental' with the supplied initial value and the firings of the provided 'Event',
426433-- using the combining function to produce a patch.
@@ -566,6 +573,7 @@ instance MonadHold t m => MonadHold t (ReaderT r m) where
566573 holdIncremental a0 = lift . holdIncremental a0
567574 buildDynamic a0 = lift . buildDynamic a0
568575 headE = lift . headE
576+ now = lift now
569577
570578instance (MonadSample t m, Monoid r) => MonadSample t (WriterT r m) where
571579 sample = lift . sample
@@ -576,6 +584,7 @@ instance (MonadHold t m, Monoid r) => MonadHold t (WriterT r m) where
576584 holdIncremental a0 = lift . holdIncremental a0
577585 buildDynamic a0 = lift . buildDynamic a0
578586 headE = lift . headE
587+ now = lift now
579588
580589instance MonadSample t m => MonadSample t (StateT s m) where
581590 sample = lift . sample
@@ -586,6 +595,7 @@ instance MonadHold t m => MonadHold t (StateT s m) where
586595 holdIncremental a0 = lift . holdIncremental a0
587596 buildDynamic a0 = lift . buildDynamic a0
588597 headE = lift . headE
598+ now = lift now
589599
590600instance MonadSample t m => MonadSample t (ExceptT e m) where
591601 sample = lift . sample
@@ -596,6 +606,7 @@ instance MonadHold t m => MonadHold t (ExceptT e m) where
596606 holdIncremental a0 = lift . holdIncremental a0
597607 buildDynamic a0 = lift . buildDynamic a0
598608 headE = lift . headE
609+ now = lift now
599610
600611instance (MonadSample t m, Monoid w) => MonadSample t (RWST r w s m) where
601612 sample = lift . sample
@@ -606,6 +617,7 @@ instance (MonadHold t m, Monoid w) => MonadHold t (RWST r w s m) where
606617 holdIncremental a0 = lift . holdIncremental a0
607618 buildDynamic a0 = lift . buildDynamic a0
608619 headE = lift . headE
620+ now = lift now
609621
610622instance MonadSample t m => MonadSample t (ContT r m) where
611623 sample = lift . sample
@@ -616,6 +628,7 @@ instance MonadHold t m => MonadHold t (ContT r m) where
616628 holdIncremental a0 = lift . holdIncremental a0
617629 buildDynamic a0 = lift . buildDynamic a0
618630 headE = lift . headE
631+ now = lift now
619632
620633--------------------------------------------------------------------------------
621634-- Convenience functions
src/Reflex/Dynamic.hs+1−1
@@ -348,7 +348,7 @@ factorDyn d = buildDynamic (sample (current d) >>= holdKey) update where
348348--
349349-- > demuxed (demux d) k === fmap (== k) d
350350--
351−-- However, when getDemuxed is used multiple times, the complexity is only
351+-- However, when 'demuxed' is used multiple times, the complexity is only
352352-- /O(log(n))/, rather than /O(n)/ for fmap.
353353data Demux t k = Demux { demuxValue :: Behavior t k
354354 , demuxSelector :: EventSelector t (Const2 k Bool)
src/Reflex/DynamicWriter/Base.hs+13−4
@@ -22,6 +22,7 @@ module Reflex.DynamicWriter.Base
2222import Control.Monad.Exception
2323import Control.Monad.Identity
2424import Control.Monad.IO.Class
25+import Control.Monad.Morph
2526import Control.Monad.Primitive
2627import Control.Monad.Reader
2728import Control.Monad.Ref
@@ -51,9 +52,6 @@ import Reflex.Query.Class
5152import Reflex.Requester.Class
5253import Reflex.TriggerEvent.Class
5354
54−instance MonadTrans (DynamicWriterT t w) where
55− lift = DynamicWriterT . lift
56−
5755mapIncrementalMapValues :: (Reflex t, Patch (p v), Patch (p v'), PatchTarget (p v) ~ f v, PatchTarget (p v') ~ f v', Functor p, Functor f) => (v -> v') -> Incremental t (p v) -> Incremental t (p v')
5856mapIncrementalMapValues f = unsafeMapIncremental (fmap f) (fmap f)
5957
@@ -90,7 +88,18 @@ mergeDynIncrementalWithMove a = unsafeBuildIncremental (mapM (sample . current)
9088
9189-- | A basic implementation of 'DynamicWriter'.
9290newtype DynamicWriterT t w m a = DynamicWriterT { unDynamicWriterT :: StateT [Dynamic t w] m a }
93− deriving (Functor, Applicative, Monad, MonadIO, MonadFix, MonadAsyncException, MonadException) -- The list is kept in reverse order
91+ -- The list is kept in reverse order
92+ deriving
93+ ( Functor
94+ , Applicative
95+ , Monad
96+ , MonadTrans
97+ , MFunctor
98+ , MonadIO
99+ , MonadFix
100+ , MonadAsyncException
101+ , MonadException
102+ )
94103
95104deriving instance MonadHold t m => MonadHold t (DynamicWriterT t w m)
96105deriving instance MonadSample t m => MonadSample t (DynamicWriterT t w m)
src/Reflex/EventWriter/Base.hs+14−4
@@ -35,6 +35,7 @@ import Reflex.TriggerEvent.Class
3535
3636import Control.Monad.Exception
3737import Control.Monad.Identity
38+import Control.Monad.Morph
3839import Control.Monad.Primitive
3940import Control.Monad.Reader
4041import Control.Monad.Ref
@@ -97,7 +98,17 @@ data EventWriterState t w = EventWriterState
9798
9899-- | A basic implementation of 'EventWriter'.
99100newtype EventWriterT t w m a = EventWriterT { unEventWriterT :: StateT (EventWriterState t w) m a }
100− deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadException, MonadAsyncException)
101+ deriving
102+ ( Functor
103+ , Applicative
104+ , Monad
105+ , MonadTrans
106+ , MFunctor
107+ , MonadFix
108+ , MonadIO
109+ , MonadException
110+ , MonadAsyncException
111+ )
101112
102113-- | Run a 'EventWriterT' action.
103114runEventWriterT :: forall t m w a. (Reflex t, Monad m, Semigroup w) => EventWriterT t w m a -> m (a, Event t w)
@@ -117,9 +128,6 @@ instance (Reflex t, Monad m, Semigroup w) => EventWriter t w (EventWriterT t w m
117128 , _eventWriterState_told = (tellId myId :=> w) : _eventWriterState_told old
118129 }
119130
120−instance MonadTrans (EventWriterT t w) where
121− lift = EventWriterT . lift
122−
123131instance MonadSample t m => MonadSample t (EventWriterT t w m) where
124132 sample = lift . sample
125133
@@ -134,6 +142,8 @@ instance MonadHold t m => MonadHold t (EventWriterT t w m) where
134142 buildDynamic a0 = lift . buildDynamic a0
135143 {-# INLINABLE headE #-}
136144 headE = lift . headE
145+ {-# INLINABLE now #-}
146+ now = lift now
137147
138148instance (Reflex t, Adjustable t m, MonadHold t m, Semigroup w) => Adjustable t (EventWriterT t w m) where
139149 runWithReplace = runWithReplaceEventWriterTWith $ \dm0 dm' -> lift $ runWithReplace dm0 dm'
src/Reflex/Host/Headless.hs+11−9
@@ -7,6 +7,7 @@
77module Reflex.Host.Headless where
88
99import Control.Concurrent.Chan (newChan, readChan)
10+import Control.Monad (unless)
1011import Control.Monad.Fix (MonadFix, fix)
1112import Control.Monad.IO.Class (MonadIO, liftIO)
1213import Control.Monad.Primitive (PrimMonad)
@@ -15,7 +16,7 @@ import Data.Dependent.Sum (DSum (..), (==>))
1516import Data.Foldable (for_)
1617import Data.Functor.Identity (Identity(..))
1718import Data.IORef (IORef, readIORef)
18−import Data.Maybe (catMaybes)
19+import Data.Maybe (catMaybes, fromMaybe)
1920import Data.Traversable (for)
2021
2122import Reflex
@@ -82,32 +83,33 @@ runHeadlessApp guest =
8283 -- 'Nothing' if the guest application hasn't subscribed to this event.
8384 mPostBuildTrigger <- readRef postBuildTriggerRef
8485
85− -- When there is a subscriber to the post-build event, fire the event.
86− for_ mPostBuildTrigger $ \postBuildTrigger ->
87− fire [postBuildTrigger :=> Identity ()] $ pure ()
88−
8986 -- Subscribe to an 'Event' of that the guest application can use to
9087 -- request application shutdown. We'll check whether this 'Event' is firing
9188 -- to determine whether to terminate.
9289 shutdown <- subscribeEvent result
9390
91+ -- When there is a subscriber to the post-build event, fire the event.
92+ soa <- for mPostBuildTrigger $ \postBuildTrigger ->
93+ fire [postBuildTrigger :=> Identity ()] $ isFiring shutdown
94+
9495 -- The main application loop. We wait for new events and fire those that
9596 -- have subscribers. If we detect a shutdown request, the application
9697 -- terminates.
97− fix $ \loop -> do
98+ unless (or (fromMaybe [] soa)) $ fix $ \loop -> do
9899 -- Read the next event (blocking).
99100 ers <- liftIO $ readChan events
100101 stop <- do
101102 -- Fire events that have subscribers.
102103 fireEventTriggerRefs fc ers $
103104 -- Check if the shutdown 'Event' is firing.
104− readEvent shutdown >>= \case
105− Nothing -> pure False
106− Just _ -> pure True
105+ isFiring shutdown
107106 if or stop
108107 then pure ()
109108 else loop
110109 where
110+ isFiring ev = readEvent ev >>= \case
111+ Nothing -> pure False
112+ Just _ -> pure True
111113 -- Use the given 'FireCommand' to fire events that have subscribers
112114 -- and call the callback for the 'TriggerInvocation' of each.
113115 fireEventTriggerRefs
src/Reflex/PerformEvent/Base.hs+2
@@ -164,6 +164,8 @@ instance (ReflexHost t, MonadHold t m) => MonadHold t (PerformEventT t m) where
164164 buildDynamic getV0 v' = PerformEventT $ lift $ buildDynamic getV0 v'
165165 {-# INLINABLE headE #-}
166166 headE = PerformEventT . lift . headE
167+ {-# INLINABLE now #-}
168+ now = PerformEventT . lift $ now
167169
168170instance (MonadRef (HostFrame t), ReflexHost t) => MonadRef (PerformEventT t m) where
169171 type Ref (PerformEventT t m) = Ref (HostFrame t)
src/Reflex/PostBuild/Base.hs+2
@@ -83,6 +83,8 @@ instance MonadHold t m => MonadHold t (PostBuildT t m) where
8383 buildDynamic a0 = lift . buildDynamic a0
8484 {-# INLINABLE headE #-}
8585 headE = lift . headE
86+ {-# INLINABLE now #-}
87+ now = lift now
8688
8789instance PerformEvent t m => PerformEvent t (PostBuildT t m) where
8890 type Performable (PostBuildT t m) = Performable m
src/Reflex/Profiled.hs+1
@@ -184,6 +184,7 @@ instance MonadHold t m => MonadHold (ProfiledTimeline t) (ProfiledM m) where
184184 holdIncremental v0 (Event_Profiled v') = ProfiledM $ Incremental_Profiled <$> holdIncremental v0 v'
185185 buildDynamic (ProfiledM v0) (Event_Profiled v') = ProfiledM $ Dynamic_Profiled <$> buildDynamic v0 v'
186186 headE (Event_Profiled e) = ProfiledM $ Event_Profiled <$> headE e
187+ now = ProfiledM $ Event_Profiled <$> now
187188
188189instance MonadSample t m => MonadSample (ProfiledTimeline t) (ProfiledM m) where
189190 sample (Behavior_Profiled b) = ProfiledM $ sample b
src/Reflex/Pure.hs+4
@@ -211,3 +211,7 @@ instance (Enum t, HasTrie t, Ord t) => MonadHold (Pure t) ((->) t) where
211211 Just x -> fromMaybe lastValue $ apply x lastValue
212212
213213 headE = slowHeadE
214+ now t = Event $ guard . (t ==)
215+
216+
217+
src/Reflex/Query/Base.hs+4
@@ -22,6 +22,7 @@ module Reflex.Query.Base
2222import Control.Applicative (liftA2)
2323import Control.Monad.Exception
2424import Control.Monad.Fix
25+import Control.Monad.Morph
2526import Control.Monad.Primitive
2627import Control.Monad.Reader
2728import Control.Monad.Ref
@@ -239,6 +240,9 @@ instance (Reflex t, MonadFix m, Group q, Additive q, Query q, Eq q, MonadHold t
239240instance MonadTrans (QueryT t q) where
240241 lift = QueryT . lift . lift . lift
241242
243+instance MFunctor (QueryT t q) where
244+ hoist = mapQueryT
245+
242246instance PrimMonad m => PrimMonad (QueryT t q m) where
243247 type PrimState (QueryT t q m) = PrimState m
244248 primitive = lift . primitive
src/Reflex/Requester/Base.hs+95−26
@@ -1,6 +1,7 @@
11-- | This module provides 'RequesterT', the standard implementation of
22-- 'Requester'.
33{-# LANGUAGE AllowAmbiguousTypes #-}
4+{-# LANGUAGE ApplicativeDo #-}
45{-# LANGUAGE CPP #-}
56{-# LANGUAGE EmptyDataDecls #-}
67{-# LANGUAGE ExistentialQuantification #-}
@@ -32,6 +33,7 @@ module Reflex.Requester.Base
3233 , requesterDataToList
3334 , singletonRequesterData
3435 , matchResponsesWithRequests
36+ , matchResponseMapWithRequests
3537 , multiEntry
3638 , unMultiEntry
3739 , requesting'
@@ -49,12 +51,14 @@ import Reflex.TriggerEvent.Class
4951import Control.Applicative (liftA2)
5052import Control.Monad.Exception
5153import Control.Monad.Identity
54+import Control.Monad.Morph
5255import Control.Monad.Primitive
5356import Control.Monad.Reader
5457import Control.Monad.Ref
5558import Control.Monad.State.Strict
5659import Data.Bits
5760import Data.Coerce
61+import Data.Constraint
5862import Data.Dependent.Map (DMap)
5963import qualified Data.Dependent.Map as DMap
6064import Data.Dependent.Sum (DSum (..))
@@ -80,19 +84,51 @@ newtype TagMap (f :: * -> *) = TagMap (IntMap Any)
8084
8185newtype RequesterData f = RequesterData (TagMap (Entry f))
8286
87+emptyRequesterData :: RequesterData f
88+emptyRequesterData = RequesterData $ TagMap IntMap.empty
89+
8390data RequesterDataKey a where
8491 RequesterDataKey_Single :: {-# UNPACK #-} !(MyTag (Single a)) -> RequesterDataKey a
8592 RequesterDataKey_Multi :: {-# UNPACK #-} !(MyTag Multi) -> {-# UNPACK #-} !Int -> !(RequesterDataKey a) -> RequesterDataKey a --TODO: Don't put a second Int here (or in the other Multis); use a single Int instead
86− RequesterDataKey_Multi2 :: {-# UNPACK #-} !(MyTag (Multi2 k)) -> !(Some k) -> {-# UNPACK #-} !Int -> !(RequesterDataKey a) -> RequesterDataKey a
93+ RequesterDataKey_Multi2 :: GCompare k => {-# UNPACK #-} !(MyTag (Multi2 k)) -> !(Some k) -> {-# UNPACK #-} !Int -> !(RequesterDataKey a) -> RequesterDataKey a
8794 RequesterDataKey_Multi3 :: {-# UNPACK #-} !(MyTag Multi3) -> {-# UNPACK #-} !Int -> {-# UNPACK #-} !Int -> !(RequesterDataKey a) -> RequesterDataKey a
8895
8996singletonRequesterData :: RequesterDataKey a -> f a -> RequesterData f
9097singletonRequesterData rdk v = case rdk of
9198 RequesterDataKey_Single k -> RequesterData $ singletonTagMap k $ Entry v
9299 RequesterDataKey_Multi k k' k'' -> RequesterData $ singletonTagMap k $ Entry $ IntMap.singleton k' $ singletonRequesterData k'' v
93− RequesterDataKey_Multi2 k k' k'' k''' -> RequesterData $ singletonTagMap k $ Entry $ Map.singleton k' $ IntMap.singleton k'' $ singletonRequesterData k''' v
100+ RequesterDataKey_Multi2 k k' k'' k''' -> RequesterData $ singletonTagMap k $ Entry $ Multi2Contents
101+ { _multi2Contents_values = Map.singleton k' $ IntMap.singleton k'' $ singletonRequesterData k''' v
102+ , _multi2Contents_dict = Dict
103+ }
94104 RequesterDataKey_Multi3 k k' k'' k''' -> RequesterData $ singletonTagMap k $ Entry $ IntMap.singleton k' $ IntMap.singleton k'' $ singletonRequesterData k''' v
95105
106+mergeRequesterData :: RequesterData f -> RequesterData f -> RequesterData f
107+mergeRequesterData (RequesterData a) (RequesterData b) = RequesterData $ mergeTagMap a b
108+
109+mergeTagMap :: forall f. TagMap (Entry f) -> TagMap (Entry f) -> TagMap (Entry f)
110+mergeTagMap (TagMap m) (TagMap n) =
111+ TagMap $ IntMap.unionWithKey (g' combiner) m n
112+ where
113+ combiner :: forall a. MyTag a -> Entry f a -> Entry f a -> Entry f a
114+ combiner k (Entry a) (Entry b) = Entry $ case myKeyType k of
115+ MyTagType_Single -> a
116+ MyTagType_Multi -> IntMap.unionWith mergeRequesterData a b
117+ MyTagType_Multi2 -> case _multi2Contents_dict a of
118+ Dict -> Multi2Contents
119+ { _multi2Contents_values = Map.unionWith (IntMap.unionWith mergeRequesterData) (_multi2Contents_values a) (_multi2Contents_values b)
120+ , _multi2Contents_dict = Dict
121+ }
122+ MyTagType_Multi3 -> IntMap.unionWith (IntMap.unionWith mergeRequesterData) a b
123+ g' :: (forall a. MyTag a -> Entry f a -> Entry f a -> Entry f a) -> Int -> Any -> Any -> Any
124+ g' f rawKey a b =
125+ let k = MyTag rawKey :: MyTag a
126+ fromAny :: Any -> Entry f a
127+ fromAny = unsafeCoerce
128+ toAny :: Entry f a -> Any
129+ toAny = unsafeCoerce
130+ in toAny $ f k (fromAny a) (fromAny b)
131+
96132requesterDataToList :: RequesterData f -> [DSum RequesterDataKey f]
97133requesterDataToList (RequesterData m) = do
98134 k :=> Entry e <- tagMapToList m
@@ -102,11 +138,12 @@ requesterDataToList (RequesterData m) = do
102138 (k', e') <- IntMap.toList e
103139 k'' :=> e'' <- requesterDataToList e'
104140 return $ RequesterDataKey_Multi k k' k'' :=> e''
105− MyTagType_Multi2 -> do
106− (k', e') <- Map.toList e
107− (k'', e'') <- IntMap.toList e'
108− k''' :=> e''' <- requesterDataToList e''
109− return $ RequesterDataKey_Multi2 k k' k'' k''' :=> e'''
141+ MyTagType_Multi2 -> case _multi2Contents_dict e of
142+ Dict -> do
143+ (k', e') <- Map.toList $ _multi2Contents_values e
144+ (k'', e'') <- IntMap.toList e'
145+ k''' :=> e''' <- requesterDataToList e''
146+ return $ RequesterDataKey_Multi2 k k' k'' k''' :=> e'''
110147 MyTagType_Multi3 -> do
111148 (k', e') <- IntMap.toList e
112149 (k'', e'') <- IntMap.toList e'
@@ -134,7 +171,13 @@ traverseRequesterData f (RequesterData m) = RequesterData <$> traverseTagMapWith
134171 go k (Entry request) = Entry <$> case myKeyType k of
135172 MyTagType_Single -> f request
136173 MyTagType_Multi -> traverse (traverseRequesterData f) request
137− MyTagType_Multi2 -> traverse (traverse (traverseRequesterData f)) request
174+ MyTagType_Multi2 -> case request of
175+ Multi2Contents { _multi2Contents_values = request', _multi2Contents_dict = Dict } -> do
176+ v <- traverse (traverse (traverseRequesterData f)) request'
177+ pure $ Multi2Contents
178+ { _multi2Contents_values = v
179+ , _multi2Contents_dict = Dict
180+ }
138181 MyTagType_Multi3 -> traverse (traverse (traverseRequesterData f)) request
139182
140183-- | 'traverseRequesterData' with its arguments flipped
@@ -178,9 +221,14 @@ instance MyTagTypeOffset Multi3 where
178221type family EntryContents request a where
179222 EntryContents request (Single a) = request a
180223 EntryContents request Multi = IntMap (RequesterData request)
181− EntryContents request (Multi2 k) = Map (Some k) (IntMap (RequesterData request))
224+ EntryContents request (Multi2 k) = Multi2Contents k request
182225 EntryContents request Multi3 = IntMap (IntMap (RequesterData request))
183226
227+data Multi2Contents k request = Multi2Contents
228+ { _multi2Contents_dict :: {-# UNPACK #-} !(Dict (GCompare k)) -- This is a Dict instead of an existential context because we only want to use it in certain circumstances
229+ , _multi2Contents_values :: {-# UNPACK #-} !(Map (Some k) (IntMap (RequesterData request)))
230+ }
231+
184232newtype Entry request x = Entry { unEntry :: EntryContents request x }
185233
186234{-# INLINE singleEntry #-}
@@ -309,7 +357,7 @@ tagRequest req = do
309357 return t
310358
311359{-# INLINE responseFromTag #-}
312−responseFromTag :: Monad m => MyTagWrap response (Entry response x) -> RequesterT t request response m (Event t (Entry response x))
360+responseFromTag :: forall m t request response x. Monad m => MyTagWrap response (Entry response x) -> RequesterT t request response m (Event t (Entry response x))
313361responseFromTag (MyTagWrap t) = do
314362 responses :: EventSelectorInt t Any <- RequesterT ask
315363 return $ (unsafeCoerce :: Event t Any -> Event t (Entry response x)) $ selectInt responses t
@@ -317,6 +365,9 @@ responseFromTag (MyTagWrap t) = do
317365instance MonadTrans (RequesterT t request response) where
318366 lift = RequesterT . lift . lift
319367
368+instance MFunctor (RequesterT t request response) where
369+ hoist f = RequesterT . hoist (hoist f) . unRequesterT
370+
320371instance PerformEvent t m => PerformEvent t (RequesterT t request response m) where
321372 type Performable (RequesterT t request response m) = Performable m
322373 performEvent_ = lift . performEvent_
@@ -436,9 +487,9 @@ traverseDMapWithKeyWithAdjustRequesterTWith base mapPatch weakenPatchWith patchN
436487 let responses :: EventSelector t (Const2 (Some k) (IntMap (RequesterData response)))
437488 responses = fanMap $ fmapCheap unpack response
438489 unpack :: Entry response (Multi2 k) -> Map (Some k) (IntMap (RequesterData response))
439− unpack = unEntry
490+ unpack = _multi2Contents_values . unEntry
440491 pack :: Map (Some k) (IntMap (RequesterData request)) -> Entry request (Multi2 k)
441− pack = Entry
492+ pack m = Entry $ Multi2Contents { _multi2Contents_values = m, _multi2Contents_dict = Dict }
442493 f' :: forall a. k a -> Compose ((,) Int) v a -> m (Compose ((,) (Event t (IntMap (RequesterData request)))) v' a)
443494 f' k (Compose (n, v)) = do
444495 (result, myRequests) <- runRequesterT (f k v) $ mapMaybeCheap (IntMap.lookup n) $ select responses (Const2 (Some k))
@@ -459,9 +510,6 @@ traverseDMapWithKeyWithAdjustRequesterTWith base mapPatch weakenPatchWith patchN
459510data Decoder rawResponse response =
460511 forall a. Decoder (RequesterDataKey a) (rawResponse -> response a)
461512
462−-- | Matches incoming responses with previously-sent requests
463−-- and uses the provided request "decoder" function to process
464−-- incoming responses.
465513matchResponsesWithRequests
466514 :: forall t rawRequest rawResponse request response m.
467515 ( MonadFix m
@@ -480,7 +528,30 @@ matchResponsesWithRequests
480528 )
481529 -- ^ A map of outgoing wire-format requests and an event of responses keyed
482530 -- by the 'RequesterData' key of the associated outgoing request
483−matchResponsesWithRequests f send recv = do
531+matchResponsesWithRequests f send recv = matchResponseMapWithRequests f send $ uncurry Map.singleton <$> recv
532+
533+-- | Matches incoming responses with previously-sent requests
534+-- and uses the provided request "decoder" function to process
535+-- incoming responses.
536+matchResponseMapWithRequests
537+ :: forall t rawRequest rawResponse request response m.
538+ ( MonadFix m
539+ , MonadHold t m
540+ , Reflex t
541+ )
542+ => (forall a. request a -> (rawRequest, rawResponse -> response a))
543+ -- ^ Given a request (from 'Requester'), produces the wire format of the
544+ -- request and a function used to process the associated response
545+ -> Event t (RequesterData request)
546+ -- ^ The outgoing requests
547+ -> Event t (Map Int rawResponse)
548+ -- ^ A map of incoming responses, tagged by an identifying key
549+ -> m ( Event t (Map Int rawRequest)
550+ , Event t (RequesterData response)
551+ )
552+ -- ^ A map of outgoing wire-format requests and an event of responses keyed
553+ -- by the 'RequesterData' key of the associated outgoing request
554+matchResponseMapWithRequests f send recv = do
484555 rec nextId <- hold 1 $ fmap (\(next, _, _) -> next) outgoing
485556 waitingFor :: Incremental t (PatchMap Int (Decoder rawResponse response)) <-
486557 holdIncremental mempty $ leftmost
@@ -522,17 +593,15 @@ matchResponsesWithRequests f send recv = do
522593 processIncoming
523594 :: Incremental t (PatchMap Int (Decoder rawResponse response))
524595 -- A map of outstanding expected responses
525− -> Event t (Int, rawResponse)
596+ -> Event t (Map Int rawResponse)
526597 -- A incoming response paired with its identifying key
527598 -> Event t (RequesterData response, PatchMap Int v)
528599 -- The decoded response and a patch that clears the outstanding responses queue
529− processIncoming waitingFor inc = flip push inc $ \(n, rawRsp) -> do
600+ processIncoming waitingFor inc = flip push inc $ \rspMap -> do
530601 wf <- sample $ currentIncremental waitingFor
531− case Map.lookup n wf of
532− Nothing -> return Nothing -- TODO How should lookup failures be handled here? They shouldn't ever happen..
533− Just (Decoder k rspF) -> do
534− let rsp = rspF rawRsp
535− return $ Just
536− ( singletonRequesterData k rsp
537− , PatchMap $ Map.singleton n Nothing
538− )
602+ let match rawRsp (Decoder k rspF) =
603+ let rsp = rspF rawRsp
604+ in singletonRequesterData k rsp
605+ matches = Map.intersectionWith match rspMap wf
606+ pure $ if Map.null matches then Nothing else Just
607+ (Map.foldl' mergeRequesterData emptyRequesterData matches, PatchMap $ Nothing <$ matches)
src/Reflex/Spider/Internal.hs+36−1
@@ -307,7 +307,20 @@ data CacheSubscribed x a
307307#endif
308308 }
309309
310−
310+nowSpiderEventM :: (HasSpiderTimeline x) => EventM x (R.Event (SpiderTimeline x) ())
311+nowSpiderEventM =
312+ SpiderEvent <$> now
313+
314+now :: (MonadIO m, Defer (Some Clear) m, HasSpiderTimeline x
315+ ) => m (Event x ())
316+now = do
317+ nowOrNot <- liftIO $ newIORef $ Just ()
318+ scheduleClear nowOrNot
319+ return . Event $ \_ -> do
320+ occ <- liftIO . readIORef $ nowOrNot
321+ return ( EventSubscription (return ()) eventSubscribedNow
322+ , occ
323+ )
311324
312325-- | Construct an 'Event' whose value is guaranteed not to be recomputed
313326-- repeatedly
@@ -594,6 +607,16 @@ eventSubscribedNever = EventSubscribed
594607 , eventSubscribedWhoCreated = return ["never"]
595608#endif
596609 }
610+eventSubscribedNow :: EventSubscribed x
611+eventSubscribedNow = EventSubscribed
612+ { eventSubscribedHeightRef = zeroRef
613+ , eventSubscribedRetained = toAny ()
614+#ifdef DEBUG_CYCLES
615+ , eventSubscribedGetParents = return []
616+ , eventSubscribedHasOwnHeightRef = False
617+ , eventSubscribedWhoCreated = return ["now"]
618+#endif
619+ }
597620
598621eventSubscribedFan :: FanSubscribed x k v -> EventSubscribed x
599622eventSubscribedFan !subscribed = EventSubscribed
@@ -2497,6 +2520,8 @@ instance HasSpiderTimeline x => Reflex.Class.MonadHold (SpiderTimeline x) (Event
24972520 {-# INLINABLE headE #-}
24982521 headE = R.slowHeadE
24992522-- headE (SpiderEvent e) = SpiderEvent <$> Reflex.Spider.Internal.headE e
2523+ {-# INLINABLE now #-}
2524+ now = nowSpiderEventM
25002525
25012526instance Reflex.Class.MonadSample (SpiderTimeline x) (SpiderPullM x) where
25022527 {-# INLINABLE sample #-}
@@ -2518,6 +2543,9 @@ instance HasSpiderTimeline x => Reflex.Class.MonadHold (SpiderTimeline x) (Spide
25182543 {-# INLINABLE headE #-}
25192544 headE = R.slowHeadE
25202545-- headE (SpiderEvent e) = SpiderPushM $ SpiderEvent <$> Reflex.Spider.Internal.headE e
2546+ {-# INLINABLE now #-}
2547+ now = SpiderPushM nowSpiderEventM
2548+
25212549
25222550instance HasSpiderTimeline x => Monad (Reflex.Class.Dynamic (SpiderTimeline x)) where
25232551 {-# INLINE return #-}
@@ -2581,6 +2609,9 @@ instance HasSpiderTimeline x => Reflex.Class.MonadHold (SpiderTimeline x) (Spide
25812609 buildDynamic getV0 e = runFrame . runSpiderHostFrame $ Reflex.Class.buildDynamic getV0 e
25822610 {-# INLINABLE headE #-}
25832611 headE e = runFrame . runSpiderHostFrame $ Reflex.Class.headE e
2612+ {-# INLINABLE now #-}
2613+ now = runFrame . runSpiderHostFrame $ Reflex.Class.now
2614+
25842615
25852616instance HasSpiderTimeline x => Reflex.Class.MonadSample (SpiderTimeline x) (SpiderHostFrame x) where
25862617 sample = SpiderHostFrame . readBehaviorUntracked . unSpiderBehavior --TODO: This can cause problems with laziness, so we should get rid of it if we can
@@ -2597,6 +2628,8 @@ instance HasSpiderTimeline x => Reflex.Class.MonadHold (SpiderTimeline x) (Spide
25972628 {-# INLINABLE headE #-}
25982629 headE = R.slowHeadE
25992630-- headE (SpiderEvent e) = SpiderHostFrame $ SpiderEvent <$> Reflex.Spider.Internal.headE e
2631+ {-# INLINABLE now #-}
2632+ now = SpiderHostFrame Reflex.Class.now
26002633
26012634instance HasSpiderTimeline x => Reflex.Class.MonadSample (SpiderTimeline x) (SpiderHost x) where
26022635 {-# INLINABLE sample #-}
@@ -2617,6 +2650,8 @@ instance HasSpiderTimeline x => Reflex.Class.MonadHold (SpiderTimeline x) (Refle
26172650 buildDynamic getV0 e = Reflex.Spider.Internal.ReadPhase $ Reflex.Class.buildDynamic getV0 e
26182651 {-# INLINABLE headE #-}
26192652 headE e = Reflex.Spider.Internal.ReadPhase $ Reflex.Class.headE e
2653+ {-# INLINABLE now #-}
2654+ now = Reflex.Spider.Internal.ReadPhase Reflex.Class.now
26202655
26212656--------------------------------------------------------------------------------
26222657-- Deprecated items
src/Reflex/TriggerEvent/Base.hs+2
@@ -122,6 +122,8 @@ instance MonadHold t m => MonadHold t (TriggerEventT t m) where
122122 buildDynamic a0 = lift . buildDynamic a0
123123 {-# INLINABLE headE #-}
124124 headE = lift . headE
125+ {-# INLINABLE now #-}
126+ now = lift now
125127
126128instance Adjustable t m => Adjustable t (TriggerEventT t m) where
127129 {-# INLINABLE runWithReplace #-}
test/Headless.hsnew file+10
@@ -0,0 +1,10 @@
1+module Main where
2+
3+import Reflex
4+import Reflex.Host.Headless (runHeadlessApp)
5+
6+main :: IO ()
7+main = do
8+ runHeadlessApp $ do
9+ pb <- getPostBuild
10+ performEvent (pure <$> pb)
test/Reflex/Plan/Pure.hs+1
@@ -41,6 +41,7 @@ instance MonadHold (Pure Int) PurePlan where
4141 holdIncremental initial = liftPlan . holdIncremental initial
4242 buildDynamic getInitial = liftPlan . buildDynamic getInitial
4343 headE = liftPlan . headE
44+ now = liftPlan now
4445
4546instance MonadSample (Pure Int) PurePlan where
4647 sample = liftPlan . sample
test/Reflex/Test/Micro.hs+8−2
@@ -322,8 +322,14 @@ testCases =
322322 let e = never <$ switch b
323323 return $ void e
324324 lazyHold
325−
326−
325+ , testE "now-1" $ do
326+ e1 <- events1
327+ switchHoldPromptly never . pushAlways (\a -> fmap (a <$) now) $ e1
328+ , testE "now-2" $ do
329+ e1 <- events1
330+ let e = pushAlways (\a -> if a == "a" then now else return never) e1
331+ x <- accumDyn (<>) never e
332+ return . coincidence $ updated x
327333 ] where
328334
329335 events1, events2, events3 :: TestPlan t m => m (Event t String)