on develop help sign in
Steer people away from `switchPromptly*`

Create non-prompt counterparts, even though these helper functions are dramatically simpler, so that no one mistakenly thinks we are endorsing the prompt versions over their non-prompt equivalents. This is important because the "prompt" semantics are rarely needed, yet are much more difficult to use correctly. Also, rename `switchPromptly` and `switchPromptOnly` to `switchHoldr*`. This helpers show how they are far more related to each other (having identical types) than they are to plain `switch`. One small downside of this change is that it obscures relationship between `switchPromptlyDyn` and `switchHoldDyn`. This is deemd worth it as the use of the `current` behavior to avoid holding really does make a semantic difference: Old inner events prior to the first outer event firing will never be lost.

commitc2b8ccd3e5625acc725beec7b867628eaec99f04 authorJohn Ericson <John.Ericson@Obsidian.Systems> dateMon 29 Jan 2018, 16:13 (9 years ago) parentc04f3c9
5 files changed+88−39
Quickref.md+2−2 src/Reflex/Class.hs+44−11 src/Reflex/Dynamic.hs+16 test/Reflex/Test/CrossImpl.hs+13−13 test/Reflex/Test/Micro.hs+13−13
Quickref.md+2−2
@@ -159,7 +159,7 @@ For Events, the returned Event fires whenever the latest Event supplied by the w
159159[ ] switch :: Behavior (Event a) -> Event a
160160
161161-- Flatten Dyanmic-of-Event to Event. New Event is used immediately.
162−[ ] switchPromptlyDyn :: Dynamic (Event a) -> Event a
162+[ ] switchDyn :: Dynamic (Event a) -> Event a
163163
164164-- Flatten Event-of-Event to Event that fires when both wrapper AND new Event fire.
165165[ ] coincidence :: Event (Event a) -> Event a
@@ -182,7 +182,7 @@ For Events, the returned Event fires whenever the latest Event supplied by the w
182182
183183-- Similar to above, for Events. Created Event initially tracks the first argument.
184184-- At switchover, the output Event immediately tracks the new Event.
185−[H] switchPromptly :: Event a -> Event (Event a) -> m (Event a)
185+[H] switchHold :: Event a -> Event (Event a) -> m (Event a)
186186```
187187
188188## Typeclasses to introspect and modify an FRP network.
src/Reflex/Class.hs+44−11
@@ -62,9 +62,10 @@ module Reflex.Class
6262 , EitherTag (..)
6363 , eitherToDSum
6464 , dsumToEither
65− -- ** Switching between 'Event's
66− , switchPromptly
67− , switchPromptOnly
65+ -- ** Collapsing 'Event . Event'
66+ , switchHold
67+ , switchHoldPromptly
68+ , switchHoldPromptOnly
6869 -- ** Using 'Event's to sample 'Behavior's
6970 , tag
7071 , attach
@@ -142,6 +143,8 @@ module Reflex.Class
142143 , tagCheap
143144 , mergeWithCheap
144145 , mergeWithCheap'
146+ , switchPromptly
147+ , switchPromptOnly
145148 -- * Slow, but general, implementations
146149 , slowHeadE
147150#ifdef SPECIALIZE_TO_SPIDERTIMELINE_GLOBAL
@@ -825,22 +828,45 @@ fanThese e =
825828fanMap :: (Reflex t, Ord k) => Event t (Map k a) -> EventSelector t (Const2 k a)
826829fanMap = fan . fmap mapToDMap
827830
828−-- | Switches to the new event whenever it receives one. Whenever a new event
829−-- is provided, if it is firing, its value will be the resulting event's value;
830−-- if it is not firing, but the old one is, the old one's value will be used.
831−switchPromptly :: (Reflex t, MonadHold t m) => Event t a -> Event t (Event t a) -> m (Event t a)
832−switchPromptly ea0 eea = do
831+-- | Switches to the new event whenever it receives one. Only the old event is
832+-- considered the moment a new one is switched in; the output event will fire at
833+-- that moment if only if the old event does.
834+--
835+-- Because the simultaneous firing case is irrelevant, this function imposes
836+-- laxer "timing requirements" on the overall circuit, avoiding many potential
837+-- cyclic dependency / metastability failures. It's also more performant. Use
838+-- this rather than 'switchHoldPromptly' and 'switchHoldPromptOnly' unless you
839+-- are absolutely sure you need to act on the new event in the coincidental
840+-- case.
841+switchHold :: (Reflex t, MonadHold t m) => Event t a -> Event t (Event t a) -> m (Event t a)
842+switchHold ea0 eea = switch <$> hold ea0 eea
843+
844+-- | Switches to the new event whenever it receives one. Whenever a new event is
845+-- provided, if it is firing, its value will be the resulting event's value; if
846+-- it is not firing, but the old one is, the old one's value will be used.
847+--
848+-- 'switchHold', by always forwarding the old event the moment it is switched
849+-- out, avoids many potential cyclic dependency problems / metastability
850+-- problems. It's also more performant. Use it instead unless you are sure you
851+-- cannot.
852+switchHoldPromptly :: (Reflex t, MonadHold t m) => Event t a -> Event t (Event t a) -> m (Event t a)
853+switchHoldPromptly ea0 eea = do
833854 bea <- hold ea0 eea
834855 let eLag = switch bea
835856 eCoincidences = coincidence eea
836857 return $ leftmost [eCoincidences, eLag]
837858
838−-- | 'switch'es to a new event whenever it receives one. At the moment of
859+-- | switches to a new event whenever it receives one. At the moment of
839860-- switching, the old event will be ignored if it fires, and the new one will be
840861-- used if it fires; this is the opposite of 'switch', which will use only the
841862-- old value.
842−switchPromptOnly :: (Reflex t, MonadHold t m) => Event t a -> Event t (Event t a) -> m (Event t a)
843−switchPromptOnly e0 e' = do
863+--
864+-- 'switchHold', by always forwarding the old event the moment it is switched
865+-- out, avoids many potential cyclic dependency problems / metastability
866+-- problems. It's also more performant. Use it instead unless you are sure you
867+-- cannot.
868+switchHoldPromptOnly :: (Reflex t, MonadHold t m) => Event t a -> Event t (Event t a) -> m (Event t a)
869+switchHoldPromptOnly e0 e' = do
844870 eLag <- switch <$> hold e0 e'
845871 return $ coincidence $ leftmost [e', eLag <$ eLag]
846872
@@ -1275,3 +1301,10 @@ sequenceThese t = case t of
12751301 This ma -> fmap This ma
12761302 These ma mb -> liftM2 These ma mb
12771303 That mb -> fmap That mb
1304+
1305+{-# 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'. " #-}
1306+switchPromptly :: (Reflex t, MonadHold t m) => Event t a -> Event t (Event t a) -> m (Event t a)
1307+switchPromptly = switchHoldPromptly
1308+{-# 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'. " #-}
1309+switchPromptOnly :: (Reflex t, MonadHold t m) => Event t a -> Event t (Event t a) -> m (Event t a)
1310+switchPromptOnly = switchHoldPromptOnly
src/Reflex/Dynamic.hs+16
@@ -30,6 +30,7 @@ module Reflex.Dynamic
3030 , constDyn
3131 , count
3232 , toggle
33+ , switchDyn
3334 , switchPromptlyDyn
3435 , tagPromptlyDyn
3536 , attachPromptlyDyn
@@ -181,10 +182,25 @@ count e = holdDyn 0 =<< zipListWithEvent const (iterate (+1) 1) e
181182toggle :: (Reflex t, MonadHold t m, MonadFix m) => Bool -> Event t a -> m (Dynamic t Bool)
182183toggle = foldDyn (const not)
183184
185+-- | Switches to the new 'Event' whenever it receives one. Only the old event is
186+-- considered the moment a new one is switched in; the output event will fire at
187+-- that moment if only if the old event does.
188+--
189+-- Prefer this to 'switchPromptlyDyn' where possible. The lack of doing double
190+-- work when the outer and (new) inner fires means this imposes fewer "timing
191+-- requirements" and thus is far more easy to use without introducing fresh
192+-- failure cases. 'switchDyn' is also more performant.
193+switchDyn :: forall t a. Reflex t => Dynamic t (Event t a) -> Event t a
194+switchDyn d = switch (current d)
195+
184196-- | Switches to the new 'Event' whenever it receives one. Switching occurs
185197-- *before* the inner 'Event' fires - so if the 'Dynamic' changes and both the
186198-- old and new inner Events fire simultaneously, the output will fire with the
187199-- value of the *new* 'Event'.
200+--
201+-- Prefer 'switchDyn' to this where possible. The timing requirements that
202+-- switching before imposes are likely to bring down your app unless you are
203+-- very careful. 'switchDyn' is also more performant.
188204switchPromptlyDyn :: forall t a. Reflex t => Dynamic t (Event t a) -> Event t a
189205switchPromptlyDyn de =
190206 let eLag = switch $ current de
test/Reflex/Test/CrossImpl.hs+13−13
@@ -155,35 +155,35 @@ testCases =
155155 let e' = leftmost [e, e]
156156 e'' <- switch <$> hold e' (fmap (const e) e)
157157 return (b, e'')
158− , (,) "switchPromptly-1" $ TestCase (Map.singleton 0 (0 :: Int), Map.fromList [(1, "qwer"), (2, "lkj")]) $ \(b, e) -> do
158+ , (,) "switchHoldPromptly-1" $ TestCase (Map.singleton 0 (0 :: Int), Map.fromList [(1, "qwer"), (2, "lkj")]) $ \(b, e) -> do
159159 let e' = fmap (const e) e
160− e'' <- switchPromptly never e'
160+ e'' <- switchHoldPromptly never e'
161161 return (b, e'')
162− , (,) "switchPromptly-2" $ TestCase (Map.singleton 0 (0 :: Int), Map.fromList [(1, "qwer"), (2, "lkj")]) $ \(b, e) -> do
162+ , (,) "switchHoldPromptly-2" $ TestCase (Map.singleton 0 (0 :: Int), Map.fromList [(1, "qwer"), (2, "lkj")]) $ \(b, e) -> do
163163 let e' = fmap (const e) e
164− e'' <- switchPromptly never $ leftmost [e', e']
164+ e'' <- switchHoldPromptly never $ leftmost [e', e']
165165 return (b, e'')
166− , (,) "switchPromptly-3" $ TestCase (Map.singleton 0 (0 :: Int), Map.fromList [(1, "qwer"), (2, "lkj")]) $ \(b, e) -> do
166+ , (,) "switchHoldPromptly-3" $ TestCase (Map.singleton 0 (0 :: Int), Map.fromList [(1, "qwer"), (2, "lkj")]) $ \(b, e) -> do
167167 let e' = leftmost [e, e]
168− e'' <- switchPromptly never (fmap (const e) e')
168+ e'' <- switchHoldPromptly never (fmap (const e) e')
169169 return (b, e'')
170− , (,) "switchPromptly-4" $ TestCase (Map.singleton 0 (0 :: Int), Map.fromList [(1, "qwer"), (2, "lkj"), (3, "asdf")]) $ \(b, e) -> do
170+ , (,) "switchHoldPromptly-4" $ TestCase (Map.singleton 0 (0 :: Int), Map.fromList [(1, "qwer"), (2, "lkj"), (3, "asdf")]) $ \(b, e) -> do
171171 let e' = leftmost [e, e]
172− e'' <- switchPromptly never (fmap (const e') e)
172+ e'' <- switchHoldPromptly never (fmap (const e') e)
173173 return (b, e'')
174174 , (,) "switch-5" $ TestCase (Map.singleton 0 (0 :: Int), Map.fromList [(1, "qwer"), (2, "lkj")]) $ \(b, e) -> do
175175 let e' = leftmost [e, e]
176176 e'' <- switch <$> hold never (fmap (const e') e)
177177 return (b, e'')
178− , (,) "switchPromptly-5" $ TestCase (Map.singleton 0 (0 :: Int), Map.fromList [(1, "qwer"), (2, "lkj")]) $ \(b, e) -> do
178+ , (,) "switchHoldPromptly-5" $ TestCase (Map.singleton 0 (0 :: Int), Map.fromList [(1, "qwer"), (2, "lkj")]) $ \(b, e) -> do
179179 let e' = flip push e $ \_ -> do
180180 Just <$> headE e
181− e'' <- switchPromptly never e'
181+ e'' <- switchHoldPromptly never e'
182182 return (b, e'')
183− , (,) "switchPromptly-6" $ TestCase (Map.singleton 0 (0 :: Int), Map.fromList [(1, "qwer"), (2, "lkj")]) $ \(b, e) -> do
183+ , (,) "switchHoldPromptly-6" $ TestCase (Map.singleton 0 (0 :: Int), Map.fromList [(1, "qwer"), (2, "lkj")]) $ \(b, e) -> do
184184 let e' = flip pushAlways e $ \_ -> do
185− switchPromptly e never
186− e'' <- switchPromptly never e'
185+ switchHoldPromptly e never
186+ e'' <- switchHoldPromptly never e'
187187 return (b, e'')
188188 , (,) "coincidence-1" $ TestCase (Map.singleton 0 (0 :: Int), Map.fromList [(1, "qwer"), (2, "lkj")]) $ \(b, e) -> do
189189 let e' = flip pushAlways e $ \_ -> return e
test/Reflex/Test/Micro.hs+13−13
@@ -128,36 +128,36 @@ testCases =
128128 return $ coincidence $ flip pushAlways e $ const $ do
129129 switch <$> hold ("x" <$ e) (e <$ e)
130130
131− , testE "switchPromptly-1" $ do
131+ , testE "switchHoldPromptly-1" $ do
132132 e <- events1
133133 let e' = e <$ e
134− switchPromptly never $ e <$ e'
134+ switchHoldPromptly never $ e <$ e'
135135
136− , testE "switchPromptly-2" $ do
136+ , testE "switchHoldPromptly-2" $ do
137137 e <- events1
138− switchPromptly never $ deep (e <$ e)
138+ switchHoldPromptly never $ deep (e <$ e)
139139
140− , testE "switchPromptly-3" $ do
140+ , testE "switchHoldPromptly-3" $ do
141141 e <- events1
142− switchPromptly never $ (e <$ deep e)
142+ switchHoldPromptly never $ (e <$ deep e)
143143
144− , testE "switchPromptly-4" $ do
144+ , testE "switchHoldPromptly-4" $ do
145145 e <- events1
146− switchPromptly never $ (deep e <$ e)
146+ switchHoldPromptly never $ (deep e <$ e)
147147
148148 , testE "switch-5" $ do
149149 e <- events1
150150 switch <$> hold never (deep e <$ e)
151151
152− , testE "switchPromptly-5" $ do
152+ , testE "switchHoldPromptly-5" $ do
153153 e <- events1
154− switchPromptly never $ flip push e $
154+ switchHoldPromptly never $ flip push e $
155155 const (Just <$> headE e)
156156
157− , testE "switchPromptly-6" $ do
157+ , testE "switchHoldPromptly-6" $ do
158158 e <- events1
159− switchPromptly never $ flip pushAlways e $
160− const (switchPromptly e never)
159+ switchHoldPromptly never $ flip pushAlways e $
160+ const (switchHoldPromptly e never)
161161
162162 , testE "coincidence-1" $ do
163163 e <- events1