on ae@dev-script help sign in
reflex/src/Reflex/Dynamic.hs
531 lines 23 kB history raw
1{-# LANGUAGE CPP #-}
2{-# LANGUAGE DataKinds #-}
3{-# LANGUAGE FlexibleContexts #-}
4{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE GADTs #-}
6{-# LANGUAGE LambdaCase #-}
7{-# LANGUAGE MultiParamTypeClasses #-}
8{-# LANGUAGE NoMonomorphismRestriction #-}
9{-# LANGUAGE PolyKinds #-}
10{-# LANGUAGE RankNTypes #-}
11{-# LANGUAGE RecursiveDo #-}
12{-# LANGUAGE ScopedTypeVariables #-}
13{-# LANGUAGE StandaloneDeriving #-}
14{-# LANGUAGE TypeFamilies #-}
15{-# LANGUAGE TypeOperators #-}
16{-# LANGUAGE UndecidableInstances #-}
17#ifdef USE_REFLEX_OPTIMIZER
18{-# OPTIONS_GHC -fplugin=Reflex.Optimizer #-}
19#endif
20-- |
21-- Module:
22-- Reflex.Dynamic
23-- Description:
24-- This module contains various functions for working with 'Dynamic' values.
25-- 'Dynamic' and its primitives have been moved to the 'Reflex' class.
26module Reflex.Dynamic
27 ( -- * Basics
28 Dynamic -- Abstract so we can preserve the law that the current value is always equal to the most recent update
29 , current
30 , updated
31 , holdDyn
32 , mapDynM
33 , forDynM
34 , constDyn
35 , count
36 , toggle
37 , switchDyn
38 , switchPromptlyDyn
39 , tagPromptlyDyn
40 , attachPromptlyDyn
41 , attachPromptlyDynWith
42 , attachPromptlyDynWithMaybe
43 , maybeDyn
44 , eitherDyn
45 , factorDyn
46 , scanDyn
47 , scanDynMaybe
48 , holdUniqDyn
49 , holdUniqDynBy
50 , improvingMaybe
51 , foldDyn
52 , foldDynM
53 , foldDynMaybe
54 , foldDynMaybeM
55 , joinDynThroughMap
56 , traceDyn
57 , traceDynWith
58 , splitDynPure
59 , distributeMapOverDynPure
60 , distributeDMapOverDynPure
61 , distributeListOverDynPure
62 , Demux
63 , demux
64 , demuxed
65 -- * Miscellaneous
66 -- Things that probably aren't very useful:
67 , HList (..)
68 , FHList (..)
69 , collectDynPure
70 , RebuildSortedHList (..)
71 , IsHList (..)
72 , AllAreFunctors (..)
73 , HListPtr (..)
74 , distributeFHListOverDynPure
75 -- * Unsafe
76 , unsafeDynamic
77 ) where
79import Data.Functor.Compose
80import Data.Functor.Misc
81import Reflex.Class
83import Control.Monad
84import Control.Monad.Fix
85import Control.Monad.Identity
86import Data.Align
87import Data.Dependent.Map (DMap)
88import qualified Data.Dependent.Map as DMap
89import Data.Dependent.Sum (DSum (..))
90import Data.GADT.Compare (GCompare (..), GEq (..), GOrdering (..))
91import Data.Map (Map)
92import Data.Maybe
93import Data.Monoid ((<>))
94import Data.These
95import Data.Type.Equality ((:~:) (..))
97import Debug.Trace
99-- | Map a sampling function over a 'Dynamic'.
100mapDynM :: forall t m a b. (Reflex t, MonadHold t m) => (forall m'. MonadSample t m' => a -> m' b) -> Dynamic t a -> m (Dynamic t b)
101mapDynM f d = buildDynamic (f =<< sample (current d)) $ pushAlways f (updated d)
103-- | Flipped version of 'mapDynM'
104forDynM :: forall t m a b. (Reflex t, MonadHold t m) => Dynamic t a -> (forall m'. MonadSample t m' => a -> m' b) -> m (Dynamic t b)
105forDynM d f = mapDynM f d
107-- | Create a new 'Dynamic' that only signals changes if the values actually
108-- changed.
109holdUniqDyn :: (Reflex t, MonadHold t m, MonadFix m, Eq a) => Dynamic t a -> m (Dynamic t a)
110holdUniqDyn = holdUniqDynBy (==)
112-- | Create a new 'Dynamic' that changes only when the underlying 'Dynamic'
113-- changes and the given function returns 'False' when given both the old and
114-- the new values.
115holdUniqDynBy :: (Reflex t, MonadHold t m, MonadFix m) => (a -> a -> Bool) -> Dynamic t a -> m (Dynamic t a)
116holdUniqDynBy eq = scanDynMaybe id (\new old -> if new `eq` old then Nothing else Just new)
118-- | @/Dynamic Maybe/@ that can only update from @/Nothing/@ to @/Just/@ or @/Just/@ to @/Just/@ (i.e., cannot revert to @/Nothing/@)
119improvingMaybe :: (Reflex t, MonadHold t m, MonadFix m) => Dynamic t (Maybe a) -> m (Dynamic t (Maybe a))
120improvingMaybe = scanDynMaybe id (\new _ -> if isJust new then Just new else Nothing)
122-- | Create a 'Dynamic' that accumulates values from another 'Dynamic'. This
123-- function does not force its input 'Dynamic' until the output 'Dynamic' is
124-- forced.
125scanDyn :: (Reflex t, MonadHold t m, MonadFix m) => (a -> b) -> (a -> b -> b) -> Dynamic t a -> m (Dynamic t b)
126scanDyn z f = scanDynMaybe z (\a b -> Just $ f a b)
128-- | Like 'scanDyn', but the the accumulator function may decline to update the
129-- result 'Dynamic''s value.
130scanDynMaybe :: (Reflex t, MonadHold t m, MonadFix m) => (a -> b) -> (a -> b -> Maybe b) -> Dynamic t a -> m (Dynamic t b)
131scanDynMaybe z f d = do
132 rec d' <- buildDynamic (z <$> sample (current d)) $ flip push (updated d) $ \a -> do
133 b <- sample $ current d'
134 return $ f a b
135 return d'
137-- | Create a 'Dynamic' using the initial value and change it each time the
138-- 'Event' occurs using a folding function on the previous value and the value
139-- of the 'Event'.
140foldDyn :: (Reflex t, MonadHold t m, MonadFix m) => (a -> b -> b) -> b -> Event t a -> m (Dynamic t b)
141foldDyn = accumDyn . flip
143-- | Like 'foldDyn', but the combining function is a 'PushM' action, so it
144-- can 'sample' existing 'Behaviors' and 'hold' new ones.
145foldDynM :: (Reflex t, MonadHold t m, MonadFix m) => (a -> b -> PushM t b) -> b -> Event t a -> m (Dynamic t b)
146foldDynM = accumMDyn . flip
148-- | Create a 'Dynamic' using the provided initial value and change it each time
149-- the provided 'Event' occurs, using a function to combine the old value with
150-- the 'Event''s value. If the function returns 'Nothing', the value is not
151-- changed; this is distinct from returning 'Just' the old value, since the
152-- 'Dynamic''s 'updated' 'Event' will fire in the 'Just' case, and will not fire
153-- in the 'Nothing' case.
154foldDynMaybe :: (Reflex t, MonadHold t m, MonadFix m) => (a -> b -> Maybe b) -> b -> Event t a -> m (Dynamic t b)
155foldDynMaybe = accumMaybeDyn . flip
157-- | Like 'foldDynMaybe', but the combining function is a 'PushM' action, so it
158-- can 'sample' existing 'Behaviors' and 'hold' new ones.
159foldDynMaybeM :: (Reflex t, MonadHold t m, MonadFix m) => (a -> b -> PushM t (Maybe b)) -> b -> Event t a -> m (Dynamic t b)
160foldDynMaybeM = accumMaybeMDyn . flip
162-- | Create a new 'Dynamic' that counts the occurrences of the 'Event'.
163count :: (Reflex t, MonadHold t m, MonadFix m, Num b) => Event t a -> m (Dynamic t b)
164count e = holdDyn 0 =<< zipListWithEvent const (iterate (+1) 1) e
166-- | Create a new 'Dynamic' using the initial value that flips its
167-- value every time the 'Event' occurs.
168toggle :: (Reflex t, MonadHold t m, MonadFix m) => Bool -> Event t a -> m (Dynamic t Bool)
169toggle = foldDyn (const not)
171-- | Switches to the new 'Event' whenever it receives one. Only the old event is
172-- considered the moment a new one is switched in; the output event will fire at
173-- that moment if only if the old event does.
174--
175-- Prefer this to 'switchPromptlyDyn' where possible. The lack of doing double
176-- work when the outer and (new) inner fires means this imposes fewer "timing
177-- requirements" and thus is far more easy to use without introducing fresh
178-- failure cases. 'switchDyn' is also more performant.
179switchDyn :: forall t a. Reflex t => Dynamic t (Event t a) -> Event t a
180switchDyn d = switch (current d)
182-- | Switches to the new 'Event' whenever it receives one. Switching occurs
183-- __before__ the inner 'Event' fires - so if the 'Dynamic' changes and both the
184-- old and new inner Events fire simultaneously, the output will fire with the
185-- value of the __new__ 'Event'.
186--
187-- Prefer 'switchDyn' to this where possible. The timing requirements that
188-- switching before imposes are likely to bring down your app unless you are
189-- very careful. 'switchDyn' is also more performant.
190switchPromptlyDyn :: forall t a. Reflex t => Dynamic t (Event t a) -> Event t a
191switchPromptlyDyn de =
192 let eLag = switch $ current de
193 eCoincidences = coincidence $ updated de
194 in leftmost [eCoincidences, eLag]
196-- | Split a 'Dynamic' pair into a pair of 'Dynamic's
197splitDynPure :: Reflex t => Dynamic t (a, b) -> (Dynamic t a, Dynamic t b)
198splitDynPure d = (fmap fst d, fmap snd d)
200-- | Convert a 'Map' with 'Dynamic' elements into a 'Dynamic' of a 'Map' with
201-- non-'Dynamic' elements.
202distributeMapOverDynPure :: (Reflex t, Ord k) => Map k (Dynamic t v) -> Dynamic t (Map k v)
203distributeMapOverDynPure = fmap dmapToMap . distributeDMapOverDynPure . mapWithFunctorToDMap
205-- | Convert a list with 'Dynamic' elements into a 'Dynamic' of a list with
206-- non-'Dynamic' elements, preserving the order of the elements.
207{-# DEPRECATED distributeListOverDynPure "Use 'distributeListOverDyn' instead" #-}
208distributeListOverDynPure :: Reflex t => [Dynamic t v] -> Dynamic t [v]
209distributeListOverDynPure = distributeListOverDyn
211--TODO: Generalize this to functors other than Maps
212-- | Combine a 'Dynamic' of a 'Map' of 'Dynamic's into a 'Dynamic'
213-- with the current values of the 'Dynamic's in a map.
214joinDynThroughMap :: forall t k a. (Reflex t, Ord k) => Dynamic t (Map k (Dynamic t a)) -> Dynamic t (Map k a)
215joinDynThroughMap = (distributeMapOverDynPure =<<)
217-- | Print the value of the 'Dynamic' when it is first read and on each
218-- subsequent change that is observed (as 'traceEvent'), prefixed with the
219-- provided string. This should /only/ be used for debugging.
220--
221-- Note: Just like Debug.Trace.trace, the value will only be shown if something
222-- else in the system is depending on it.
223traceDyn :: (Reflex t, Show a) => String -> Dynamic t a -> Dynamic t a
224traceDyn s = traceDynWith $ \x -> s <> ": " <> show x
226-- | Print the result of applying the provided function to the value
227-- of the 'Dynamic' when it is first read and on each subsequent change
228-- that is observed (as 'traceEvent'). This should /only/ be used for
229-- debugging.
230--
231-- Note: Just like Debug.Trace.trace, the value will only be shown if something
232-- else in the system is depending on it.
233traceDynWith :: Reflex t => (a -> String) -> Dynamic t a -> Dynamic t a
234traceDynWith f d =
235 let e' = traceEventWith f $ updated d
236 getV0 = do
237 x <- sample $ current d
238 trace (f x) $ return x
239 in unsafeBuildDynamic getV0 e'
241-- | Replace the value of the 'Event' with the current value of the 'Dynamic'
242-- each time the 'Event' occurs.
243--
244-- Note: @/tagPromptlyDyn d e/@ differs from @/tag (current d) e/@ in the case that @/e/@ is firing
245-- at the same time that @/d/@ is changing. With @/tagPromptlyDyn d e/@, the __new__ value of @/d/@
246-- will replace the value of @/e/@, whereas with @/tag (current d) e/@, the __old__ value
247-- will be used, since the 'Behavior' won't be updated until the end of the frame.
248-- Additionally, this means that the output 'Event' may not be used to directly change
249-- the input 'Dynamic', because that would mean its value depends on itself. __When creating__
250-- __cyclic data flows, generally @/tag (current d) e/@ is preferred.__
251tagPromptlyDyn :: Reflex t => Dynamic t a -> Event t b -> Event t a
252tagPromptlyDyn = attachPromptlyDynWith const
254-- | Attach the current value of the 'Dynamic' to the value of the
255-- 'Event' each time it occurs.
256--
257-- Note: @/attachPromptlyDyn d/@ is not the same as @/attach (current d)/@. See 'tagPromptlyDyn' for details.
258attachPromptlyDyn :: Reflex t => Dynamic t a -> Event t b -> Event t (a, b)
259attachPromptlyDyn = attachPromptlyDynWith (,)
261-- | Combine the current value of the 'Dynamic' with the value of the
262-- 'Event' each time it occurs.
263--
264-- Note: @/attachPromptlyDynWith f d/@ is not the same as @/attachWith f (current d)/@. See 'tagPromptlyDyn' for details.
265attachPromptlyDynWith :: Reflex t => (a -> b -> c) -> Dynamic t a -> Event t b -> Event t c
266attachPromptlyDynWith f = attachPromptlyDynWithMaybe $ \a b -> Just $ f a b
268-- | Create a new 'Event' by combining the value at each occurrence with the
269-- current value of the 'Dynamic' value and possibly filtering if the combining
270-- function returns 'Nothing'.
271--
272-- Note: @/attachPromptlyDynWithMaybe f d/@ is not the same as @/attachWithMaybe f (current d)/@. See 'tagPromptlyDyn' for details.
273attachPromptlyDynWithMaybe :: Reflex t => (a -> b -> Maybe c) -> Dynamic t a -> Event t b -> Event t c
274attachPromptlyDynWithMaybe f d e =
275 let e' = attach (current d) e
276 in fforMaybe (align e' $ updated d) $ \case
277 This (a, b) -> f a b -- Only the tagging event is firing, so use that
278 These (_, b) a -> f a b -- Both events are firing, so use the newer value
279 That _ -> Nothing -- The tagging event isn't firing, so don't fire
281-- | Factor a @/Dynamic t (Maybe a)/@ into a @/Dynamic t (Maybe (Dynamic t a))/@,
282-- such that the outer 'Dynamic' is updated only when the "Maybe"'s constructor
283-- chages from 'Nothing' to 'Just' or vice-versa. Whenever the constructor
284-- becomes 'Just', an inner 'Dynamic' will be provided, whose value will track
285-- the 'a' inside the 'Just'; when the constructor becomes 'Nothing', the
286-- existing inner 'Dynamic' will become constant, and will not change when the
287-- outer constructor changes back to 'Nothing'.
288maybeDyn :: forall t a m. (Reflex t, MonadFix m, MonadHold t m) => Dynamic t (Maybe a) -> m (Dynamic t (Maybe (Dynamic t a)))
289maybeDyn = fmap (fmap unpack) . eitherDyn . fmap pack
290 where pack = \case
291 Nothing -> Left ()
292 Just a -> Right a
293 unpack = \case
294 Left _ -> Nothing
295 Right a -> Just a
297-- | Turns a 'Dynamic t (Either a b)' into a 'Dynamic t (Either (Dynamic t a) (Dynamic t b))' such that
298-- the outer 'Dynamic' is updated only when the 'Either' constructor changes (e.g., from 'Left' to 'Right').
299eitherDyn :: 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)))
300eitherDyn = fmap (fmap unpack) . factorDyn . fmap eitherToDSum
301 where unpack :: DSum (EitherTag a b) (Compose (Dynamic t) Identity) -> Either (Dynamic t a) (Dynamic t b)
302 unpack = \case
303 LeftTag :=> Compose a -> Left $ coerceDynamic a
304 RightTag :=> Compose b -> Right $ coerceDynamic b
306-- | Factor a 'Dynamic t DSum' into a 'Dynamic' 'DSum' containing nested 'Dynamic' values.
307-- The outer 'Dynamic' updates only when the key of the 'DSum' changes, while the update of the inner
308-- 'Dynamic' represents updates within the current key.
309factorDyn :: forall t m k v. (Reflex t, MonadHold t m, GEq k)
310 => Dynamic t (DSum k v) -> m (Dynamic t (DSum k (Compose (Dynamic t) v)))
311factorDyn d = buildDynamic (sample (current d) >>= holdKey) update where
312 update :: Event t (DSum k (Compose (Dynamic t) v))
313 update = flip push (updated d) $ \(newKey :=> newVal) -> do
314 (oldKey :=> _) <- sample (current d)
315 case newKey `geq` oldKey of
316 Just Refl -> return Nothing
317 Nothing -> Just <$> holdKey (newKey :=> newVal)
319 holdKey (k :=> v) = do
320 inner' <- filterEventKey k (updated d)
321 inner <- holdDyn v inner'
322 return $ k :=> Compose inner
324--------------------------------------------------------------------------------
325-- Demux
326--------------------------------------------------------------------------------
328-- | Represents a time changing value together with an 'EventSelector' that can
329-- efficiently detect when the underlying 'Dynamic' has a particular value.
330-- This is useful for representing data like the current selection of a long
331-- list.
332--
333-- Semantically,
334--
335-- > demuxed (demux d) k === fmap (== k) d
336--
337-- However, when getDemuxed is used multiple times, the complexity is only
338-- /O(log(n))/, rather than /O(n)/ for fmap.
339data Demux t k = Demux { demuxValue :: Behavior t k
340 , demuxSelector :: EventSelector t (Const2 k Bool)
341 }
343-- | Demultiplex an input value to a 'Demux' with many outputs. At any given
344-- time, whichever output is indicated by the given 'Dynamic' will be 'True'.
345demux :: (Reflex t, Ord k) => Dynamic t k -> Demux t k
346demux k = Demux (current k)
347 (fan $ attachWith (\k0 k1 -> if k0 == k1
348 then DMap.empty
349 else DMap.fromList [Const2 k0 :=> Identity False,
350 Const2 k1 :=> Identity True])
351 (current k) (updated k))
353-- | Select a particular output of the 'Demux'; this is equivalent to (but much
354-- faster than) mapping over the original 'Dynamic' and checking whether it is
355-- equal to the given key.
356demuxed :: (Reflex t, Eq k) => Demux t k -> k -> Dynamic t Bool
357demuxed d k =
358 let e = select (demuxSelector d) (Const2 k)
359 in unsafeBuildDynamic (fmap (==k) $ sample $ demuxValue d) e
361--------------------------------------------------------------------------------
362-- collectDyn
363--------------------------------------------------------------------------------
365--TODO: This whole section is badly in need of cleanup
367-- | A heterogeneous list whose type and length are fixed statically. This is
368-- reproduced from the 'HList' package due to integration issues, and because
369-- very little other functionality from that library is needed.
370data HList (l::[*]) where
371 HNil :: HList '[]
372 HCons :: e -> HList l -> HList (e ': l)
374infixr 2 `HCons`
376type family HRevApp (l1 :: [k]) (l2 :: [k]) :: [k]
377type instance HRevApp '[] l = l
378type instance HRevApp (e ': l) l' = HRevApp l (e ': l')
380hRevApp :: HList l1 -> HList l2 -> HList (HRevApp l1 l2)
381hRevApp HNil l = l
382hRevApp (HCons x l) l' = hRevApp l (HCons x l')
384hReverse :: HList l -> HList (HRevApp l '[])
385hReverse l = hRevApp l HNil
387hBuild :: (HBuild' '[] r) => r
388hBuild = hBuild' HNil
390class HBuild' l r where
391 hBuild' :: HList l -> r
393instance (l' ~ HRevApp l '[])
394 => HBuild' l (HList l') where
395 hBuild' = hReverse
397instance HBuild' (a ': l) r
398 => HBuild' l (a->r) where
399 hBuild' l x = hBuild' (HCons x l)
401-- | Like 'HList', but with a functor wrapping each element.
402data FHList f l where
403 FHNil :: FHList f '[]
404 FHCons :: f e -> FHList f l -> FHList f (e ': l)
406instance GEq (HListPtr l) where
407 HHeadPtr `geq` HHeadPtr = Just Refl
408 HHeadPtr `geq` HTailPtr _ = Nothing
409 HTailPtr _ `geq` HHeadPtr = Nothing
410 HTailPtr a `geq` HTailPtr b = a `geq` b
412instance GCompare (HListPtr l) where -- Warning: This ordering can't change, dmapTo*HList will break
413 HHeadPtr `gcompare` HHeadPtr = GEQ
414 HHeadPtr `gcompare` HTailPtr _ = GLT
415 HTailPtr _ `gcompare` HHeadPtr = GGT
416 HTailPtr a `gcompare` HTailPtr b = a `gcompare` b
418-- | A typed index into a typed heterogeneous list.
419data HListPtr l a where
420 HHeadPtr :: HListPtr (h ': t) h
421 HTailPtr :: HListPtr t a -> HListPtr (h ': t) a
423deriving instance Eq (HListPtr l a)
424deriving instance Ord (HListPtr l a)
426fhlistToDMap :: forall (f :: * -> *) l. FHList f l -> DMap (HListPtr l) f
427fhlistToDMap = DMap.fromList . go
428 where go :: forall l'. FHList f l' -> [DSum (HListPtr l') f]
429 go = \case
430 FHNil -> []
431 FHCons h t -> (HHeadPtr :=> h) : map (\(p :=> v) -> HTailPtr p :=> v) (go t)
433-- | This class allows 'HList's and 'FHlist's to be built from regular lists;
434-- they must be contiguous and sorted.
435class RebuildSortedHList l where
436 rebuildSortedFHList :: [DSum (HListPtr l) f] -> FHList f l
437 rebuildSortedHList :: [DSum (HListPtr l) Identity] -> HList l
439instance RebuildSortedHList '[] where
440 rebuildSortedFHList l = case l of
441 [] -> FHNil
442 _ : _ -> error "rebuildSortedFHList{'[]}: empty list expected"
443 rebuildSortedHList l = case l of
444 [] -> HNil
445 _ : _ -> error "rebuildSortedHList{'[]}: empty list expected"
447instance RebuildSortedHList t => RebuildSortedHList (h ': t) where
448 rebuildSortedFHList l = case l of
449 ((HHeadPtr :=> h) : t) -> FHCons h . rebuildSortedFHList . map (\(HTailPtr p :=> v) -> p :=> v) $ t
450 _ -> error "rebuildSortedFHList{h':t}: non-empty list with HHeadPtr expected"
451 rebuildSortedHList l = case l of
452 ((HHeadPtr :=> Identity h) : t) -> HCons h . rebuildSortedHList . map (\(HTailPtr p :=> v) -> p :=> v) $ t
453 _ -> error "rebuildSortedHList{h':t}: non-empty list with HHeadPtr expected"
455dmapToHList :: forall l. RebuildSortedHList l => DMap (HListPtr l) Identity -> HList l
456dmapToHList = rebuildSortedHList . DMap.toList
458-- | Collect a hetereogeneous list whose elements are all 'Dynamic's into a
459-- single 'Dynamic' whose value represents the current values of all of the
460-- input 'Dynamic's.
461distributeFHListOverDynPure :: (Reflex t, RebuildSortedHList l) => FHList (Dynamic t) l -> Dynamic t (HList l)
462distributeFHListOverDynPure l = fmap dmapToHList $ distributeDMapOverDynPure $ fhlistToDMap l
464-- | Indicates that all elements in a type-level list are applications of the
465-- same functor.
466class AllAreFunctors (f :: a -> *) (l :: [a]) where
467 type FunctorList f l :: [*]
468 toFHList :: HList (FunctorList f l) -> FHList f l
469 fromFHList :: FHList f l -> HList (FunctorList f l)
471instance AllAreFunctors f '[] where
472 type FunctorList f '[] = '[]
473 toFHList l = case l of
474 HNil -> FHNil
475#if !defined(__GLASGOW_HASKELL__) || __GLASGOW_HASKELL__ < 800
476 _ -> error "toFHList: impossible" -- Otherwise, GHC complains of a non-exhaustive pattern match; see https://ghc.haskell.org/trac/ghc/ticket/4139
477#endif
478 fromFHList FHNil = HNil
480instance AllAreFunctors f t => AllAreFunctors f (h ': t) where
481 type FunctorList f (h ': t) = f h ': FunctorList f t
482 toFHList l = case l of
483 a `HCons` b -> a `FHCons` toFHList b
484#if !defined(__GLASGOW_HASKELL__) || __GLASGOW_HASKELL__ < 800
485 _ -> error "toFHList: impossible" -- Otherwise, GHC complains of a non-exhaustive pattern match; see https://ghc.haskell.org/trac/ghc/ticket/4139
486#endif
487 fromFHList (a `FHCons` b) = a `HCons` fromFHList b
489-- | Convert a datastructure whose constituent parts are all 'Dynamic's into a
490-- single 'Dynamic' whose value represents all the current values of the input's
491-- constituent 'Dynamic's.
492collectDynPure :: ( RebuildSortedHList (HListElems b)
493 , IsHList a, IsHList b
494 , AllAreFunctors (Dynamic t) (HListElems b)
495 , Reflex t
496 , HListElems a ~ FunctorList (Dynamic t) (HListElems b)
497 ) => a -> Dynamic t b
498collectDynPure ds = fmap fromHList $ distributeFHListOverDynPure $ toFHList $ toHList ds
500-- | Poor man's 'Generic's for product types only.
501class IsHList a where
502 type HListElems a :: [*]
503 toHList :: a -> HList (HListElems a)
504 fromHList :: HList (HListElems a) -> a
506instance IsHList (a, b) where
507 type HListElems (a, b) = [a, b]
508 toHList (a, b) = hBuild a b
509 fromHList l = case l of
510 a `HCons` b `HCons` HNil -> (a, b)
511#if !defined(__GLASGOW_HASKELL__) || __GLASGOW_HASKELL__ < 800
512 _ -> error "fromHList: impossible" -- Otherwise, GHC complains of a non-exhaustive pattern match; see https://ghc.haskell.org/trac/ghc/ticket/4139
513#endif
515instance IsHList (a, b, c, d) where
516 type HListElems (a, b, c, d) = [a, b, c, d]
517 toHList (a, b, c, d) = hBuild a b c d
518 fromHList l = case l of
519 a `HCons` b `HCons` c `HCons` d `HCons` HNil -> (a, b, c, d)
520#if !defined(__GLASGOW_HASKELL__) || __GLASGOW_HASKELL__ < 800
521 _ -> error "fromHList: impossible" -- Otherwise, GHC complains of a non-exhaustive pattern match; see https://ghc.haskell.org/trac/ghc/ticket/4139
522#endif
524instance IsHList (a, b, c, d, e, f) where
525 type HListElems (a, b, c, d, e, f) = [a, b, c, d, e, f]
526 toHList (a, b, c, d, e, f) = hBuild a b c d e f
527 fromHList l = case l of
528 a `HCons` b `HCons` c `HCons` d `HCons` e `HCons` f `HCons` HNil -> (a, b, c, d, e, f)
529#if !defined(__GLASGOW_HASKELL__) || __GLASGOW_HASKELL__ < 800
530 _ -> error "fromHList: impossible" -- Otherwise, GHC complains of a non-exhaustive pattern match; see https://ghc.haskell.org/trac/ghc/ticket/4139
531#endif