diff --git a/default.nix b/default.nix index e26e511c..51deff69 100644 --- a/default.nix +++ b/default.nix @@ -4,7 +4,7 @@ , ghc, webkitgtk3-javascriptcore, exception-transformers , webkitgtk24x, dependent-sum-template, bifunctors, bimap , raw-strings-qq, zenc, random, monad-control, keycode, hlint -, unbounded-delays +, unbounded-delays, generics-sop }: mkDerivation { @@ -34,6 +34,7 @@ mkDerivation { transformers zenc unbounded-delays + generics-sop ] ++ (if ghc.isGhcjs or false then [] else [ raw-strings-qq webkitgtk3-javascriptcore diff --git a/reflex-dom.cabal b/reflex-dom.cabal index eafbefc6..3f3be111 100644 --- a/reflex-dom.cabal +++ b/reflex-dom.cabal @@ -41,6 +41,7 @@ library dependent-sum-template >= 0.0.0.4 && < 0.1, directory == 1.2.*, exception-transformers == 0.4.*, + generics-sop >= 0.2.2 && < 0.3, ghcjs-dom >= 0.2.1 && < 0.3, -- keycode-0.2 has a bug on firefox keycode >= 0.2.1 && < 0.3, diff --git a/src/Reflex/Dom.hs b/src/Reflex/Dom.hs index 7c681a8a..641915c8 100644 --- a/src/Reflex/Dom.hs +++ b/src/Reflex/Dom.hs @@ -13,3 +13,4 @@ import Reflex.Dom.Time as X import Reflex.Dom.WebSocket as X import Reflex.Dom.Widget as X import Reflex.Dom.Xhr as X +import Reflex.Dom.RenderSumtype as X diff --git a/src/Reflex/Dom/RenderSumtype.hs b/src/Reflex/Dom/RenderSumtype.hs new file mode 100644 index 00000000..0242752d --- /dev/null +++ b/src/Reflex/Dom/RenderSumtype.hs @@ -0,0 +1,145 @@ +{-# LANGUAGE + DataKinds +, DeriveGeneric +, GADTs +, KindSignatures +, LambdaCase +, RankNTypes +, ScopedTypeVariables +, TypeOperators +#-} + +module Reflex.Dom.RenderSumtype + ( GTag, renderSumType ) + +where + +import Data.Dependent.Sum +import Data.GADT.Compare +import Data.Maybe (isJust) +import Generics.SOP + +import Reflex hiding (tag) +import Reflex.Dom.Widget.Basic +import Reflex.Dom.Old + +type GTag t = GTag_ (Code t) +newtype GTag_ t (xs :: [*]) = GTag (NS ((:~:) xs) t) + +instance GEq (GTag_ t) where + geq (GTag (Z Refl)) (GTag (Z Refl)) = Just Refl + geq (GTag (S x)) (GTag (S y)) = GTag x `geq` GTag y + geq _ _ = Nothing + +toDSum :: Generic t => t -> DSum (GTag t) (NP I) +toDSum = foo (\f b -> GTag f :=> b) . unSOP . from + where + foo :: (forall a . (NS ((:~:) a) xs) -> NP I a -> r) + -> NS (NP I) xs + -> r + foo k (Z x) = (k . Z) Refl x + foo k (S w) = foo (k . S) w + +data WrapDyn t m a = WrapDyn (m (Dynamic t (NP I a))) + +-- TODO consider using 'fan', which may bring a perf benefit +-- | Efficiently render a Dynamic containing a sum type. +-- As inputs, this takes the Dynamic to render, as well as a +-- renderAnything function which is capable of rendering +-- from a Dynamic containing any of the constructors of the +-- sum type. In contrast to dyn/widgetHold, rendering of the +-- constructors is done from a Dynamic, which avoids a full +-- re-render on every update. +-- +-- In this extended example, the user provides an arbitrary datatype, +-- UsersSumType, as well as the function renderUsersSumType. +-- > data SubState2 +-- > data SubState3a +-- > data SubState3b +-- > data SubState3c +-- > +-- > data UsersSumType +-- > = First +-- > | Second SubState2 +-- > | Third SubState3a SubState3b SubState3c +-- > deriving GHC.Generic +-- > +-- > instance Generic UsersSumType +-- > +-- > data UsersEventType +-- > +-- > renderFirst +-- > :: forall t m. MonadWidget t m +-- > => Dynamic t (NP I '[]) +-- > -> m (Event t UsersEventType) +-- > renderFirst d = undefined +-- > +-- > renderSecond +-- > :: forall t m. MonadWidget t m +-- > => Dynamic t (NP I '[SubState2]) +-- > -> m (Event t UsersEventType) +-- > renderSecond d = +-- > let dynState = ((\(I x :* Nil) -> x) <$> d) :: Dynamic t SubState2 +-- > in undefined +-- > +-- > renderThird +-- > :: forall t m. MonadWidget t m +-- > => Dynamic t (NP I '[SubState3a, SubState3b, SubState3c]) +-- > -> m (Event t UsersEventType) +-- > renderThird d = +-- > let dynA = ((\(I a :* I b :* I c :* Nil) -> a) <$> d) :: Dynamic t SubState3a +-- > dynB = ((\(I a :* I b :* I c :* Nil) -> b) <$> d) :: Dynamic t SubState3b +-- > dynC = ((\(I a :* I b :* I c :* Nil) -> c) <$> d) :: Dynamic t SubState3c +-- > in undefined +-- > +-- > renderUsersSumType +-- > :: MonadWidget t m +-- > => GTag UsersSumType a +-- > -> Dynamic t (NP I a) +-- > -> m (Event t UsersEventType) +-- > renderUsersSumType = \case +-- > GTag (Z Refl) -> renderFirst +-- > GTag (S (Z Refl)) -> renderSecond +-- > GTag (S (S (Z Refl))) -> renderThird +-- > _ -> error "this is impossible" +-- > +-- > render +-- > :: MonadWidget t m +-- > => Dynamic t UsersSumType +-- > -> m (Event t UsersEventType) +-- > render = renderSumType renderUsersSumType +renderSumType + :: forall t m u r. (MonadWidget t m, Generic u) + => (forall b. GTag u b -> Dynamic t (NP I b) -> m (Event t r)) + -> Dynamic t u + -> m (Event t r) +renderSumType renderAnything dynState = + switchPromptly never =<< + ( dyn + . fmap toAction + . uniqDynBy sameConstructor + . toNestedDyn + . fmap toDSum + ) dynState + where + toAction :: DSum (GTag u) (WrapDyn t m) -> m (Event t r) + toAction (t :=> WrapDyn x) = x >>= renderAnything t + + sameConstructor :: DSum (GTag u) a -> DSum (GTag u) a -> Bool + sameConstructor (t1 :=> _) (t2 :=> _) = isJust (t1 `geq` t2) + + toNestedDyn + :: Dynamic t (DSum (GTag u) (NP I)) + -> Dynamic t (DSum (GTag u) (WrapDyn t m)) + toNestedDyn d = makeNestedDyn <$> d + where + makeNestedDyn :: DSum (GTag u) (NP I) -> DSum (GTag u) (WrapDyn t m) + makeNestedDyn (t :=> x) = + t :=> WrapDyn (holdDyn x (eventsForTag t d)) + + eventsForTag + :: GTag u a + -> Dynamic t (DSum (GTag u) (NP I)) + -> Event t (NP I a) + eventsForTag tag = fmapMaybe tagToJust . updated + where tagToJust (t :=> x) = (\Refl -> x) <$> t `geq` tag