diff --git a/backends/pardiff/Shpadoinkle/Backend/ParDiff.hs b/backends/pardiff/Shpadoinkle/Backend/ParDiff.hs index c61aa601e6dfbf4385f1497ac23f56beda40efd2..abf36c481a9a32498d5d3bb245eb541af60ee364 100644 --- a/backends/pardiff/Shpadoinkle/Backend/ParDiff.hs +++ b/backends/pardiff/Shpadoinkle/Backend/ParDiff.hs @@ -64,8 +64,9 @@ import Data.Monoid ((<>)) import Data.Once (Once, newOnce, runOnce) import Data.Text (Text, intercalate) import Language.Javascript.JSaddle (FromJSVal (fromJSValUnchecked), - JSString, MakeObject (makeObject), - Object, ToJSString (toJSString), + JSString, JSVal, + MakeObject (makeObject), Object, + ToJSString (toJSString), ToJSVal (toJSVal), eval, fun, jsFalse, jsTrue, jsg, jsg2, liftJSM, unsafeSetProp, @@ -77,9 +78,9 @@ import UnliftIO.Concurrent (forkIO) import UnliftIO.STM (STM, atomically) import Shpadoinkle (Backend (..), Continuation, - Html (..), JSM, MonadJSM, NFData, - Prop (..), Props (..), - RawEvent (RawEvent), + Dependency (..), Html (..), JSM, + MonadJSM, NFData, Prop (..), + Props (..), RawEvent (RawEvent), RawNode (RawNode, unRawNode), fromProps, hoist, type (~>), writeUpdate) @@ -139,9 +140,12 @@ data ParVNode :: Type -> Type where ParNode :: Once JSM RawNode -> {-# UNPACK #-} !Text -> ParVProps a -> [ParVNode a] -> ParVNode a ParPotato :: Once JSM RawNode -> ParVNode a ParTextNode :: Once JSM RawNode -> {-# UNPACK #-} !Text -> ParVNode a + ParDepend :: !Dependency -> ParVNode a -> ParVNode a + type ParVProps = Props JSM + type ParVProp = Prop JSM @@ -189,6 +193,7 @@ getRaw = \case ParNode mk _ _ _ -> mk ParPotato mk -> mk ParTextNode mk _ -> mk + ParDepend _ h -> getRaw h makeProp :: Monad m => (m ~> JSM) -> TVar a -> Prop (ParDiffT a m) a -> ParVProp a @@ -295,13 +300,14 @@ patchChildren => RawNode -> [ParVNode a] -> [ParVNode a] -> ParDiffT a m [ParVNode a] patchChildren (RawNode p) [] new = liftJSM $ do forM_ new $ \newChild -> do - RawNode cRaw <- runOnce (getRaw newChild) - p # "appendChild" $ cRaw + RawNode cRaw <- runOnce $ getRaw newChild + _ <- p # "appendChild" $ cRaw + pure () pure new patchChildren _ old [] = liftJSM $ do doc <- jsg "document" tmp <- doc # "createElement" $ "div" - old' <- traverse (fmap unRawNode . runOnce . getRaw) old + old' :: [JSVal] <- traverse (fmap unRawNode . runOnce . getRaw) old void (tmp # "replaceChildren" $ old') void (tmp # "remove" $ ()) pure [] @@ -343,6 +349,12 @@ patch' parent old new = do cs'' <- patchChildren raw' cs cs' return $ ParNode raw name ps' cs'' + (ParDepend dep html, ParDepend dep' _) + | dep == dep' -> pure $ ParDepend dep html + + (ParDepend _ html, ParDepend _ html') -> + patch' parent html html' + -- node definitely has changed _ -> liftJSM $ do let RawNode p = parent @@ -357,7 +369,7 @@ interpret' . MonadJSM m => NFData a => (m ~> JSM) -> Html (ParDiffT a m) a -> ParDiffT a m (ParVNode a) -interpret' toJSM (Html h') = h' mkNode mkPotato mkText +interpret' toJSM (Html h') = h' mkNode mkDep mkPotato mkText where mkNode :: Text -> Props (ParDiffT a m) a -> [ParDiffT a m (ParVNode a)] -> ParDiffT a m (ParVNode a) mkNode name ps cs = do @@ -368,14 +380,17 @@ interpret' toJSM (Html h') = h' mkNode mkPotato mkText raw' <- doc # "createElement" $ name props toJSM i ps (RawNode raw') forM_ cs' $ \c -> do - RawNode cRaw <- runOnce (getRaw c) - raw' # "appendChild" $ cRaw + RawNode cRaw <- runOnce $ getRaw c + raw' # "appendChild" $ cRaw return (RawNode raw') let p = Props (makeProp toJSM i <$> getProps ps) return $ ParNode raw name p cs' + mkDep :: Dependency -> ParDiffT a m (ParVNode a) -> ParDiffT a m (ParVNode a) + mkDep d pd = ParDepend d <$> pd + mkPotato :: JSM RawNode -> ParDiffT a m (ParVNode a) mkPotato = fmap ParPotato . liftJSM . newOnce @@ -397,12 +412,11 @@ instance setup = setup' patch parent mOld new = case mOld of -- first patch - Nothing -> - liftJSM $ do - let RawNode p = parent - RawNode c <- runOnce (getRaw new) - _ <- p # "appendChild" $ c - return new + Nothing -> liftJSM $ do + let RawNode p = parent + RawNode c <- runOnce $ getRaw new + _ <- p # "appendChild" $ c + return new Just old -> patch' parent old new diff --git a/backends/pardiff/package.yaml b/backends/pardiff/package.yaml index 9a9ccd7418dfb455d89f1b2f95ecef033aa29e0a..14fd3fe9b74611be942eeb49139fecf37cb42074 100644 --- a/backends/pardiff/package.yaml +++ b/backends/pardiff/package.yaml @@ -52,6 +52,7 @@ library: - transformers-base >= 0.4.5 && < 0.5 - Shpadoinkle + - Shpadoinkle-console git: https://gitlab.com/fresheyeball/Shpadoinkle.git diff --git a/backends/snabbdom/Shpadoinkle/Backend/Snabbdom.hs b/backends/snabbdom/Shpadoinkle/Backend/Snabbdom.hs index be5ef61f517b4daf8b033e71bf5a3a9d05d71f4b..d740a40c527aeea54aa0861e36d647c871d3eb6c 100644 --- a/backends/snabbdom/Shpadoinkle/Backend/Snabbdom.hs +++ b/backends/snabbdom/Shpadoinkle/Backend/Snabbdom.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-} {-# LANGUAGE ExtendedDefaultRules #-} {-# LANGUAGE FlexibleInstances #-} @@ -30,7 +31,7 @@ module Shpadoinkle.Backend.Snabbdom ) where -import Control.Category ((.)) +import Control.Category (id, (.)) import Control.Monad.Base (MonadBase (..), liftBaseDefault) import Control.Monad.Catch (MonadCatch, MonadThrow) import Control.Monad.Reader (MonadIO, MonadReader (..), @@ -41,15 +42,15 @@ import Control.Monad.Trans.Control (ComposeSt, MonadBaseControl (..), defaultLiftBaseWith, defaultRestoreM) import Data.FileEmbed (embedStringFile) -import Data.Text (Text, split) -import Data.Traversable (for) +import Data.Map.Internal (Map (Bin, Tip)) +import Data.Text (Text, words) import GHCJS.DOM (currentDocumentUnchecked) import GHCJS.DOM.Document (createElement, getBodyUnsafe) import GHCJS.DOM.Element (setAttribute) import GHCJS.DOM.Node (appendChild) import Language.Javascript.JSaddle hiding (JSM, MonadJSM, liftJSM, (#)) -import Prelude hiding (id, (.)) +import Prelude hiding (id, words, (.)) import UnliftIO (MonadUnliftIO (..), TVar, UnliftIO (UnliftIO, unliftIO), withUnliftIO) @@ -112,54 +113,67 @@ runSnabbdom :: TVar model -> SnabbdomT model m ~> m runSnabbdom t (Snabbdom r) = runReaderT r t -props :: Monad m => NFData a => (m ~> JSM) -> TVar a -> [(Text, Prop (SnabbdomT a m) a)] -> JSM Object -props toJSM i xs = do +traverseWithKey_ :: Applicative t => (k -> a -> t ()) -> Map k a -> t () +traverseWithKey_ f = go + where + go Tip = pure () + go (Bin 1 k v _ _) = f k v + go (Bin _ k v l r) = go l *> f k v *> go r +{-# INLINE traverseWithKey_ #-} + + +props :: Monad m => NFData a => (m ~> JSM) -> TVar a -> Props (SnabbdomT a m) a -> JSM Object +props toJSM i (Props xs) = do o <- create propsObj <- create listenersObj <- create classesObj <- create attrsObj <- create hooksObj <- create - void $ xs `for` \(k, p) -> case p of - PData d -> unsafeSetProp (toJSString k) d propsObj - PPotato pot -> do - f' <- toJSVal . fun $ \_ _ -> - let - g vnode = do - vnode' <- valToObject vnode - stm <- pot . RawNode =<< unsafeGetProp "elm" vnode' - let go = atomically stm >>= writeUpdate i . hoist (toJSM . runSnabbdom i) - void $ forkIO go - in \case - [vnode] -> g vnode - [_, vnode] -> g vnode - _ -> return () - unsafeSetProp "insert" f' hooksObj - unsafeSetProp "update" f' hooksObj - - PText t -> do - t' <- toJSVal t - true <- toJSVal True - case k of - "className" | t /= "" -> forM_ (split (== ' ') t) $ \u -> - if u == mempty then pure () else unsafeSetProp (toJSString u) true classesObj - "style" | t /= "" -> unsafeSetProp (toJSString k) t' attrsObj - "type" | t /= "" -> unsafeSetProp (toJSString k) t' attrsObj - "autofocus" | t /= "" -> unsafeSetProp (toJSString k) t' attrsObj - _ -> unsafeSetProp (toJSString k) t' propsObj - - PListener f -> do - f' <- toJSVal . fun $ \_ _ -> \case - [] -> return () - ev:_ -> do - rn <- unsafeGetProp "target" =<< valToObject ev - x <- f (RawNode rn) (RawEvent ev) - writeUpdate i $ hoist (toJSM . runSnabbdom i) x - unsafeSetProp (toJSString k) f' listenersObj - - PFlag b -> do - f <- toJSVal b - unsafeSetProp (toJSString k) f propsObj + flip traverseWithKey_ xs $ \k p -> + let k' = toJSString k + in case p of + PData d -> unsafeSetProp k' d propsObj + PPotato pot -> do + f' <- toJSVal . fun $ \_ _ -> + let + g vnode = do + vnode' <- valToObject vnode + stm <- pot . RawNode =<< unsafeGetProp "elm" vnode' + let go = atomically stm >>= writeUpdate i . hoist (toJSM . runSnabbdom i) + void $ forkIO go + in \case + [vnode] -> g vnode + [_, vnode] -> g vnode + _ -> return () + unsafeSetProp "insert" f' hooksObj + unsafeSetProp "update" f' hooksObj + + PText t + | k == "className" -> forM_ (words t) $ \u -> + unsafeSetProp (toJSString u) jsTrue classesObj + | t /= "" -> do + t' <- valMakeText t + unsafeSetProp k' t' $ case k of + "style" -> attrsObj + "type" -> attrsObj + "autofocus" -> attrsObj + _ -> propsObj + | otherwise -> do + t' <- valMakeText t + unsafeSetProp k' t' propsObj + + PListener f -> do + f' <- toJSVal . fun $ \_ _ -> \case + [] -> return () + ev:_ -> do + rn <- unsafeGetProp "target" =<< valToObject ev + x <- f (RawNode rn) (RawEvent ev) + writeUpdate i $ hoist (toJSM . runSnabbdom i) x + unsafeSetProp k' f' listenersObj + + PFlag b -> + unsafeSetProp k' (toJSBool b) propsObj p <- toJSVal propsObj l <- toJSVal listenersObj @@ -178,32 +192,34 @@ instance (MonadJSM m, NFData a) => Backend (SnabbdomT a) m a where type VNode (SnabbdomT a) m = SnabVNode interpret :: (m ~> JSM) -> Html (SnabbdomT a m) a -> SnabbdomT a m SnabVNode - interpret toJSM (Html h') = h' - - (\name ps children -> do - cs <- sequence children - i <- ask; liftJSM $ do - o <- props toJSM i $ fromProps ps - jsg3 "vnode" name o cs >>= fromJSValUnchecked) - - (\mrn -> liftJSM $ do - o <- create - hook <- create - rn <- mrn - ins <- toJSVal =<< function (\_ _ -> \case - [n] -> void $ jsg2 "potato" n rn - _ -> return ()) - unsafeSetProp "insert" ins hook - hoo <- toJSVal hook - unsafeSetProp "hook" hoo o - fromJSValUnchecked =<< jsg2 "vnode" "div" o) - - (\t -> liftJSM $ fromJSValUnchecked =<< toJSVal t) + interpret toJSM (Html h') = h' mkNode mkDep mkPotato mkText + where + mkNode name ps children = do + i <- ask; liftJSM $ do + !o <- props toJSM i ps + !cs <- toJSM . runSnabbdom i $ sequence children + SnabVNode <$> jsg3 "vnode" name o cs + + mkDep = const id + + mkPotato mrn = liftJSM $ do + o <- create + hook <- create + rn <- mrn + ins <- toJSVal =<< function (\_ _ -> \case + [n] -> void $ jsg2 "potato" n rn + _ -> return ()) + unsafeSetProp "insert" ins hook + hoo <- toJSVal hook + unsafeSetProp "hook" hoo o + SnabVNode <$> jsg2 "vnode" "div" o + + mkText = liftJSM . fmap SnabVNode . valMakeText patch :: RawNode -> Maybe SnabVNode -> SnabVNode -> SnabbdomT a m SnabVNode - patch (RawNode r) f t = t <$ (liftJSM . void $ jsg2 "patchh" f' t) - where f' = maybe r unVNode f + patch (RawNode container) mPreviousNode newNode = liftJSM $ newNode <$ jsg2 "patchh" previousNode newNode + where previousNode = maybe container unVNode mPreviousNode setup :: JSM () -> JSM () diff --git a/backends/snabbdom/package.yaml b/backends/snabbdom/package.yaml index 22680ef280a3c971e35d47797e1ae3a82692861a..e9fccc653f993207b61729a24d6430a7fa635e5c 100644 --- a/backends/snabbdom/package.yaml +++ b/backends/snabbdom/package.yaml @@ -44,12 +44,14 @@ library: - exceptions - transformers-base - monad-control + - containers - mtl >= 2.2.2 && < 2.3 - unliftio >= 0.2.12 && < 0.3 - file-embed >= 0.0.11 && < 0.1 - Shpadoinkle + - Shpadoinkle-console git: https://gitlab.com/fresheyeball/Shpadoinkle.git diff --git a/backends/static/Shpadoinkle/Backend/Static.hs b/backends/static/Shpadoinkle/Backend/Static.hs index 04083b659d92950751f936306045d5b8946c5449..91a3fdbd8139b9118ad21a4d4768bafe86876e6d 100644 --- a/backends/static/Shpadoinkle/Backend/Static.hs +++ b/backends/static/Shpadoinkle/Backend/Static.hs @@ -14,12 +14,12 @@ import Control.Compactable (Compactable (fmapMaybe)) import Data.Monoid (mconcat, (<>)) import Data.Text (Text, null, unwords) -import Shpadoinkle (Html, Prop (PText), cataH, cataProp) +import Shpadoinkle (Html, Prop (PText), cataHtml, cataProp) -- | Render as @Text@ renderStatic :: Html m a -> Text -renderStatic = cataH renderTag (const mempty) id +renderStatic = cataHtml renderTag (const id) (const mempty) id renderTag :: Text -> [(Text, Prop m a)] -> [Text] -> Text diff --git a/cabal.project b/cabal.project index 29a1c6a77775a0ead8e28bcd88233948259ff897..a6a761b7c09377c8109b1dcb2955683148037e1e 100644 --- a/cabal.project +++ b/cabal.project @@ -10,6 +10,7 @@ packages: core , marketing , html , router + , streaming , widgets , examples diff --git a/core/Shpadoinkle/Continuation.hs b/core/Shpadoinkle/Continuation.hs index 21bea3fd1b027d0d64b715d2bc9142f79624ce7c..4478b99336e25bf819389c16bef4f3c06d22c4d7 100644 --- a/core/Shpadoinkle/Continuation.hs +++ b/core/Shpadoinkle/Continuation.hs @@ -17,7 +17,7 @@ module Shpadoinkle.Continuation ( -- * The Continuation Type Continuation (..) , runContinuation - , done, pur, impur, kleisli, causes, contIso + , done, pur, impur, kleisli, causes, causedBy, merge, contIso -- * The Class , Continuous (..) -- ** Hoist @@ -45,6 +45,7 @@ module Shpadoinkle.Continuation ( import Control.Arrow (first) import qualified Control.Categorical.Functor as F import Control.DeepSeq (NFData (..), force) +import Control.Monad (void) import Control.Monad.Trans.Class (MonadTrans (..)) import Control.PseudoInverseCategory (EndoIso (..)) import Data.Maybe (fromMaybe) @@ -58,7 +59,6 @@ import Language.Javascript.JSaddle (MonadJSM) import UnliftIO (MonadUnliftIO, TVar, UnliftIO, askUnliftIO, atomically, liftIO, - modifyTVar', newTVarIO, readTVar, readTVarIO, unliftIO, writeTVar) @@ -84,6 +84,7 @@ import UnliftIO.Concurrent (forkIO) -- finishes and they are all done atomically together. data Continuation m a = Continuation (a -> a, a -> m (Continuation m a)) | Rollback (Continuation m a) + | Merge (Continuation m a) | Pure (a -> a) @@ -117,6 +118,15 @@ causes :: Applicative m => m () -> Continuation m a causes m = impur (id <$ m) +causedBy :: m (Continuation m a) -> Continuation m a +causedBy = Continuation . (id,) . const + + +-- | A continuation can be forced to write its changes midflight. +merge :: Continuation m a -> Continuation m a +merge = Merge + + -- | 'runContinuation' takes a 'Continuation' and a state value and runs the whole Continuation -- as if the real state was frozen at the value given to 'runContinuation'. It performs all the -- IO actions in the stages of the Continuation and returns a pure state updating function @@ -134,6 +144,7 @@ runContinuation' f (Continuation (g, h)) x = do i <- h (f x) runContinuation' (g.f) i x runContinuation' _ (Rollback f) x = runContinuation' id f x +runContinuation' f (Merge g) x = runContinuation' f g x runContinuation' f (Pure g) _ = return (g.f) @@ -151,13 +162,15 @@ instance Continuous Continuation where hoist :: Functor m => (forall b. m b -> n b) -> Continuation m a -> Continuation n a hoist _ (Pure f) = Pure f hoist f (Rollback r) = Rollback (hoist f r) +hoist f (Merge g) = Merge (hoist f g) hoist f (Continuation (g, h)) = Continuation . (g,) $ \x -> f $ hoist f <$> h x -- | Apply a lens inside a Continuation to change the Continuation's type. liftC' :: Functor m => (a -> b -> b) -> (b -> a) -> Continuation m a -> Continuation m b -liftC' f g (Pure h) = Pure (\x -> f (h (g x)) x) -liftC' f g (Rollback r) = Rollback (liftC' f g r) +liftC' f g (Pure h) = Pure (\x -> f (h (g x)) x) +liftC' f g (Rollback r) = Rollback (liftC' f g r) +liftC' f g (Merge h) = Merge (liftC' f g h) liftC' f g (Continuation (h, i)) = Continuation (\x -> f (h (g x)) x, \x -> liftC' f g <$> i (g x)) @@ -165,6 +178,7 @@ liftC' f g (Continuation (h, i)) = Continuation (\x -> f (h (g x)) x, \x -> lift liftCMay' :: Applicative m => (a -> b -> b) -> (b -> Maybe a) -> Continuation m a -> Continuation m b liftCMay' f g (Pure h) = Pure $ \x -> maybe x (flip f x . h) $ g x liftCMay' f g (Rollback r) = Rollback (liftCMay' f g r) +liftCMay' f g (Merge h) = Merge (liftCMay' f g h) liftCMay' f g (Continuation (h, i)) = Continuation (\x -> maybe x (flip f x . h) $ g x, maybe (pure done) (fmap (liftCMay' f g) . i) . g) @@ -218,8 +232,9 @@ rightC = mapC rightC' -- | Transform a Continuation to work on 'Maybe's. If it encounters 'Nothing', then it cancels itself. maybeC' :: Applicative m => Continuation m a -> Continuation m (Maybe a) -maybeC' (Pure f) = Pure (fmap f) -maybeC' (Rollback r) = Rollback (maybeC' r) +maybeC' (Pure f) = Pure (fmap f) +maybeC' (Rollback r) = Rollback (maybeC' r) +maybeC' (Merge f) = Merge (maybeC' f) maybeC' (Continuation (f, g)) = Continuation . (fmap f,) $ \case Just x -> maybeC' <$> g x @@ -242,8 +257,9 @@ comaybe f x = fromMaybe x . f $ Just x -- when the input Continuation would replace the current value with 'Nothing', -- instead the current value is retained. comaybeC' :: Functor m => Continuation m (Maybe a) -> Continuation m a -comaybeC' (Pure f) = Pure (comaybe f) -comaybeC' (Rollback r) = Rollback (comaybeC' r) +comaybeC' (Pure f) = Pure (comaybe f) +comaybeC' (Rollback r) = Rollback (comaybeC' r) +comaybeC' (Merge f) = Merge (comaybeC' f) comaybeC' (Continuation (f,g)) = Continuation (comaybe f, fmap comaybeC' . g . Just) @@ -275,12 +291,14 @@ eitherC' f g = Continuation . (id,) $ \case Left x -> case f of Pure h -> pure (Pure (mapLeft h)) Rollback r -> pure . Rollback $ eitherC' r done + Merge h -> pure . Merge $ eitherC' h done Continuation (h, i) -> (\j -> Continuation (mapLeft h, const . pure $ eitherC' j (Rollback done))) <$> i x Right x -> case g of Pure h -> pure (Pure (mapRight h)) Rollback r -> pure . Rollback $ eitherC' done r + Merge h -> pure . Merge $ eitherC' done h Continuation (h, i) -> (\j -> Continuation (mapRight h, const . pure $ eitherC' (Rollback done) j)) <$> i x @@ -300,7 +318,8 @@ eitherC _ r (Right x) = mapC (eitherC' (pur id)) (r x) contIso :: Functor m => (a -> b) -> (b -> a) -> Continuation m a -> Continuation m b contIso f g (Continuation (h, i)) = Continuation (f.h.g, fmap (contIso f g) . i . g) contIso f g (Rollback h) = Rollback (contIso f g h) -contIso f g (Pure h) = Pure (f.h.g) +contIso f g (Merge h) = Merge (contIso f g h) +contIso f g (Pure h) = Pure (f.h.g) -- | @Continuation m@ is a Functor in the EndoIso category (where the objects @@ -313,8 +332,9 @@ instance Applicative m => F.Functor EndoIso EndoIso (Continuation m) where -- | You can combine multiple Continuations homogeneously using the 'Monoid' typeclass -- instance. The resulting Continuation will execute all the subcontinuations in parallel, -- allowing them to see each other's state updates and roll back each other's updates, --- applying all of the updates generated by all the subcontinuations atomically once --- all of them are done. +-- applying all of the unmerged updates generated by all the subcontinuations atomically once +-- all of them are done. A merge in any one of the branches will cause all of +-- the changes that branch can see to be merged. instance Applicative m => Semigroup (Continuation m a) where (Continuation (f, g)) <> (Continuation (h, i)) = Continuation (f.h, \x -> (<>) <$> g x <*> i x) @@ -328,6 +348,8 @@ instance Applicative m => Semigroup (Continuation m a) where (Continuation (f,g)) <> (Pure h) = Continuation (f.h,g) (Pure f) <> (Rollback g) = Continuation (f, const (pure (Rollback g))) (Rollback f) <> (Pure _) = Rollback f + (Merge f) <> g = Merge (f <> g) + f <> (Merge g) = Merge (f <> g) -- | Since combining Continuations homogeneously is an associative operation, @@ -342,8 +364,11 @@ writeUpdate' h model f = do m <- f (h i) case m of Continuation (g,gs) -> writeUpdate' (g . h) model gs - Pure g -> atomically (modifyTVar' model (force . g . h)) - Rollback gs -> writeUpdate' id model (const (return gs)) + Pure g -> atomically (writeTVar model . g . h =<< readTVar model) + Merge g -> do + atomically $ writeTVar model . h =<< readTVar model + writeUpdate' id model (const (return g)) + Rollback gs -> writeUpdate' id model (const (return gs)) -- | Run a Continuation on a state variable. This may update the state. @@ -351,8 +376,9 @@ writeUpdate' h model f = do -- and an asynchronous, non-blocking operation for impure updates. writeUpdate :: MonadUnliftIO m => NFData a => TVar a -> Continuation m a -> m () writeUpdate model = \case - Continuation (f,g) -> () <$ forkIO (writeUpdate' f model g) - Pure f -> atomically (modifyTVar' model (force . f)) + Continuation (f,g) -> void . forkIO $ writeUpdate' f model g + Pure f -> atomically (writeTVar model . f =<< readTVar model) + Merge f -> writeUpdate model f Rollback f -> writeUpdate model f diff --git a/core/Shpadoinkle/Core.hs b/core/Shpadoinkle/Core.hs index d4c17070c66b5d52f10bde1b3526f82a3e6f0b3f..a6c806556488d9a216a727cefff7ef5691394400 100644 --- a/core/Shpadoinkle/Core.hs +++ b/core/Shpadoinkle/Core.hs @@ -32,11 +32,11 @@ module Shpadoinkle.Core ( -- *** Listeners , listenRaw, listen, listenM, listenM_, listenC, listener -- ** Html Constructors - , h, baked, text + , h, baked, text, depending -- ** Hoists , hoistHtml, hoistProp -- ** Catamorphisms - , cataH, cataProp + , cataHtml, cataProp -- ** Utilities , mapProps, injectProps, eitherH -- * JSVal Wrappers @@ -44,6 +44,7 @@ module Shpadoinkle.Core ( -- * Backend Interface , Backend (..) , type (~>) + , Dependency(..) -- * The Shpadoinkle Primitive , shpadoinkle -- * Re-Exports @@ -64,6 +65,7 @@ import Data.Map as M (Map, singleton, toList, unionWithKey) import Data.String (IsString (..)) import Data.Text (Text, pack) +import Data.Typeable (Typeable, cast) import GHCJS.DOM.Types (JSM, MonadJSM, liftJSM) import Language.Javascript.JSaddle (FromJSVal (..), JSVal, ToJSVal (..), askJSM, runJSM) @@ -88,12 +90,21 @@ import Shpadoinkle.Continuation (Continuation, Continuous (..), newtype Html m a = Html { unHtml :: forall r. (Text -> Props m a -> [r] -> r) + -> (Dependency -> r -> r) -> (JSM RawNode -> r) -> (Text -> r) -> r } +instance Show (Html m a) where + show (Html h') = h' + (\t ps cs -> "Node " ++ show t ++ " " ++ show ps ++ " " ++ show cs) + (\d r -> "Depend (" ++ show d ++ ") (" ++ r ++ ")") + (const "Potato _") + show + + -- | Properties of a DOM node. Backend does not use attributes directly, -- but rather is focused on the more capable properties that may be set on a DOM -- node in JavaScript. If you wish to add attributes, you may do so @@ -120,6 +131,15 @@ data Prop :: (Type -> Type) -> Type -> Type where PListener :: (RawNode -> RawEvent -> JSM (Continuation m a)) -> Prop m a +instance Show (Prop m a) where + show = \case + PData _ -> "PData _" + PText t -> "PText " ++ show t + PFlag b -> "PFlag " ++ show b + PPotato _ -> "PPotato _" + PListener _ -> "PListener _" + + instance Eq (Prop m a) where x == y = case (x,y) of (PText x', PText y') -> x' == y' @@ -138,6 +158,7 @@ listenM_ k = listenC k . causes newtype Props m a = Props { getProps :: Map Text (Prop m a) } + deriving Show toProps :: Applicative m => [(Text, Prop m a)] -> Props m a @@ -318,19 +339,25 @@ cataProp d t f l p = \case -- | Construct an HTML element JSX-style. h :: Applicative m => Text -> [(Text, Prop m a)] -> [Html m a] -> Html m a -h t ps cs = Html $ \a b c -> a t (toProps ps) ((\(Html h') -> h' a b c) <$> cs) +h t ps cs = Html $ \a d b c -> a t (toProps ps) ((\(Html h') -> h' a d b c) <$> cs) {-# INLINE h #-} +-- | Memoed +depending :: (Eq a, Show a, Typeable a) => (a -> Html m c) -> (a -> Html m c) +depending f x = Html $ \a d b c -> d (Dependency x) + $ case f x of Html h' -> h' a d b c + + -- | Construct a 'Potato' from a 'JSM' action producing a 'RawNode'. baked :: JSM RawNode -> Html m a -baked jr = Html $ \_ p _ -> p jr +baked jr = Html $ \_ _ p _ -> p jr {-# INLINE baked #-} -- | Construct a text node. text :: Text -> Html m a -text t = Html $ \_ _ f -> f t +text t = Html $ \_ _ _ f -> f t {-# INLINE text #-} @@ -341,11 +368,12 @@ eitherH = eitherC -- | Fold an HTML element, i.e. transform an h-algebra into an h-catamorphism. -cataH :: (Text -> [(Text, Prop m a)] -> [b] -> b) - -> (JSM RawNode -> b) - -> (Text -> b) - -> Html m a -> b -cataH f g h' (Html h'') = h'' (\t ps cs -> f t (fromProps ps) cs) g h' +cataHtml :: (Text -> [(Text, Prop m a)] -> [r] -> r) + -> (Dependency -> r -> r) + -> (JSM RawNode -> r) + -> (Text -> r) + -> Html m a -> r +cataHtml f d g h' (Html h'') = h'' (\t ps cs -> f t (fromProps ps) cs) d g h' -- | Natural Transformation @@ -410,6 +438,17 @@ injectProps ps = mapProps (<> toProps ps) {-# INLINE injectProps #-} +data Dependency = forall a. (Eq a, Show a, Typeable a) => Dependency !a + + +instance Eq Dependency where + Dependency l == Dependency r = cast l == Just r + + +instance Show Dependency where + show (Dependency x) = "Dependency (" ++ show x ++ ")" + + -- | The Backend class describes a backend that can render 'Html'. -- Backends are generally Monad Transformers @b@ over some Monad @m@. -- @@ -418,7 +457,7 @@ class Backend b m a | b m -> a where -- | VNode type family allows backends to have their own Virtual DOM. -- As such we can change out the rendering of our Backend view -- with new backends without updating our view logic. - type VNode b m + type VNode b m :: Type -- | A backend must be able to interpret 'Html' into its own internal Virtual DOM. interpret :: (m ~> JSM) @@ -471,16 +510,25 @@ shpadoinkle toJSM toM initial model view stage = do j :: b m ~> JSM j = toJSM . toM model + sview = view + {-# SCC sview #-} + + sinterpret = interpret + {-# SCC sinterpret #-} + + spatch = patch + {-# SCC spatch #-} + go :: RawNode -> VNode b m -> a -> JSM (VNode b m) go c n a = j $ do - !m <- interpret toJSM $ view a - patch c (Just n) m + !m <- sinterpret toJSM $ sview a + spatch c (Just n) m setup @b @m @a $ do (c,n) <- j $ do c <- stage - n <- interpret toJSM $ view initial - _ <- patch c Nothing n + n <- sinterpret toJSM $ sview initial + _ <- spatch c Nothing n return (c,n) _ <- shouldUpdate (go c) n model return () diff --git a/default.nix b/default.nix index 6180bb0dd25214d6293683ebb180a19e9f6abd52..153eddadeada519d41ca3b7c470333b506d840d5 100644 --- a/default.nix +++ b/default.nix @@ -6,10 +6,12 @@ , extra ? (_: b: b) , optimize ? true , system ? builtins.currentSystem +, enableLibraryProfiling ? false +, enableExecutableProfiling ? false }: let - pkgs = import ./nix/pkgs.nix { inherit compiler isJS system chan; }; + pkgs = import ./nix/pkgs.nix { inherit compiler isJS system chan enableLibraryProfiling enableExecutableProfiling; }; util = import ./nix/util.nix { inherit pkgs compiler isJS; }; in with pkgs; with lib; @@ -37,6 +39,7 @@ in Shpadoinkle-lens Shpadoinkle-html Shpadoinkle-router + Shpadoinkle-streaming Shpadoinkle-widgets Shpadoinkle-isreal diff --git a/examples/Streaming.hs b/examples/Streaming.hs new file mode 100644 index 0000000000000000000000000000000000000000..dab995978b0e1f70b98b6af88c19ced4cc77e46d --- /dev/null +++ b/examples/Streaming.hs @@ -0,0 +1,51 @@ +{-# LANGUAGE ExtendedDefaultRules #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PackageImports #-} + + +module Main where + + +import Prelude hiding (div) + +import Control.Concurrent (threadDelay) +import Control.Monad.IO.Class (MonadIO (liftIO)) +import Data.Text (Text, pack) +import Shpadoinkle (Html, NFData, liftC) +import Shpadoinkle.Backend.ParDiff (runParDiff) +import Shpadoinkle.Html (button, div, getBody, onClickC, + text) +import Shpadoinkle.Run (runJSorWarp, simple) +import Shpadoinkle.Streaming (consumeStream) +import "streaming" Streaming (Of, Stream) +import Streaming.Prelude (repeatM) + +default (Text) + + +exampleStream :: MonadIO m => Stream (Of Int) m () +exampleStream = repeatM $ do + liftIO $ threadDelay 1000000 + return 1 + + +newtype Model = Model { streamContents :: [Int] } + deriving (Eq, Show, NFData) + + +view :: MonadIO m => Model -> Html m Model +view (Model ns) = + div + [] + [ text (pack (show ns)) + , liftC (\c m -> m { streamContents = c }) streamContents $ + button + [ onClickC (consumeStream exampleStream (return . (:))) ] + [ text "Go" ] + ] + + +main :: IO () +main = runJSorWarp 8080 $ + simple runParDiff (Model []) view getBody diff --git a/examples/TODOMVC.hs b/examples/TODOMVC.hs index bacd934aaf1fe088e361c6cc6a439d78d70c16be..1cd43e9b955fe547239e4f09f583698ea9c3482a 100644 --- a/examples/TODOMVC.hs +++ b/examples/TODOMVC.hs @@ -15,10 +15,12 @@ import Control.Lens hiding (view) import Data.Generics.Labels () import Data.String (IsString) import Data.Text hiding (count, filter, length) +import Data.Typeable (Typeable) import GHC.Generics (Generic) import Prelude hiding (div, unwords) -import Shpadoinkle (Html, JSM, NFData, readTVarIO, - shpadoinkle, text) +import Shpadoinkle (Html, JSM, NFData, depending, + readTVarIO, shpadoinkle, text) +-- import Shpadoinkle.Backend.ParDiff (runParDiff, stage) import Shpadoinkle.Backend.Snabbdom (runSnabbdom, stage) import Shpadoinkle.Html (a, addStyle, autofocus, button, button', checked, class', div, @@ -30,7 +32,6 @@ import Shpadoinkle.Html (a, addStyle, autofocus, button, placeholder, section, span, strong_, type', ul, value) import Shpadoinkle.Html.LocalStorage (manageLocalStorage) -import Shpadoinkle.Html.Memo (memo) import Shpadoinkle.Lens (generalize) import Shpadoinkle.Run (runJSorWarp) @@ -39,24 +40,24 @@ default (Text) newtype Description = Description { unDescription :: Text } - deriving stock Generic deriving newtype (Show, Read, Eq, IsString) deriving anyclass NFData + deriving stock (Generic, Typeable) deriving newtype (Show, Read, Eq, IsString) deriving anyclass NFData newtype TaskId = TaskId { unTaskId :: Int } - deriving stock Generic deriving newtype (Show, Read, Eq, Ord, Num) deriving anyclass NFData + deriving stock (Generic, Typeable) deriving newtype (Show, Read, Eq, Ord, Num) deriving anyclass NFData data Completed = Complete | Incomplete - deriving (Generic, Show, Read, Eq, NFData) + deriving (Generic, Typeable, Show, Read, Eq, NFData) data Visibility = All | Active | Completed - deriving (Generic, Show, Read, Eq, Ord, Enum, Bounded, NFData) + deriving (Generic, Typeable, Show, Read, Eq, Ord, Enum, Bounded, NFData) data Task = Task { description :: Description , completed :: Completed , taskId :: TaskId - } deriving (Generic, Show, Read, Eq, NFData) + } deriving (Generic, Typeable, Show, Read, Eq, NFData) data Model = Model @@ -64,7 +65,7 @@ data Model = Model , editing :: Maybe TaskId , visibility :: Visibility , current :: Description - } deriving (Generic, Show, Read, Eq, NFData) + } deriving (Generic, Typeable, Show, Read, Eq, NFData) emptyModel :: Model @@ -119,7 +120,7 @@ toVisible v = case v of filterHtml :: Applicative m => Visibility -> Visibility -> Html m Visibility -filterHtml = memo $ \cur item -> li_ +filterHtml = curry . depending $ \(cur, item) -> li_ [ a (href "#" : onClick (const item) : [class' ("selected", cur == item)]) [ text . pack $ show item ] ] @@ -129,11 +130,11 @@ htmlIfTasks :: Model -> [Html m a] -> [Html m a] htmlIfTasks m h' = if Prelude.null (tasks m) then [] else h' -taskView :: Applicative m => Model -> Task -> Html m Model -taskView m = memo $ \(Task (Description d) c tid) -> +taskView :: Applicative m => Maybe TaskId -> Task -> Html m Model +taskView = curry . depending $ \(currentEdit, Task (Description d) c tid) -> li [ id' . pack . show $ unTaskId tid , class' [ ("completed", c == Complete) - , ("editing", Just tid == editing m) + , ("editing", Just tid == currentEdit) ] ] [ div "view" @@ -187,7 +188,7 @@ newTaskForm model = form [ class' "todo-form", onSubmit appendItem ] todoList :: Applicative m => Model -> Html m Model -todoList model = ul "todo-list" $ taskView model <$> visibility model `toVisible` tasks model +todoList model = ul "todo-list" $ taskView (editing model) <$> visibility model `toVisible` tasks model toggleAllBtn :: Applicative m => [Html m Model] diff --git a/examples/package.yaml b/examples/package.yaml index 8268422707c8e2c14aa1b5e33663652c69948b92..a77c8402232a00cee9ed242a74c19b35f70f80c5 100644 --- a/examples/package.yaml +++ b/examples/package.yaml @@ -58,6 +58,7 @@ executables: - Shpadoinkle-html - Shpadoinkle-lens - Shpadoinkle-backend-snabbdom + - Shpadoinkle-backend-pardiff lens: main: Lens.hs @@ -289,6 +290,19 @@ executables: - Shpadoinkle-backend-pardiff - Shpadoinkle-lens + streaming: + main: Streaming.hs + other-modules: [] + source-dirs: . + dependencies: + - streaming + - text + + - Shpadoinkle + - Shpadoinkle-streaming + - Shpadoinkle-html + - Shpadoinkle-backend-pardiff + git: https://gitlab.com/fresheyeball/Shpadoinkle.git diff --git a/html/Shpadoinkle/Html/Memo.hs b/html/Shpadoinkle/Html/Memo.hs index 713e98e4c83a396362dc12eee33845de0e1e2ec3..adf787df56483e75266b6bd73cd1423c572a30b0 100644 --- a/html/Shpadoinkle/Html/Memo.hs +++ b/html/Shpadoinkle/Html/Memo.hs @@ -1,5 +1,8 @@ +{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE ExplicitForAll #-} {-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE TypeApplications #-} {-# OPTIONS_GHC -fno-warn-missing-signatures #-} @@ -21,8 +24,13 @@ module Shpadoinkle.Html.Memo ( ) where -import Data.IORef -import System.IO.Unsafe +import Data.IORef (IORef, newIORef, readIORef, writeIORef) +import Data.Typeable (Typeable) +import Shpadoinkle (Html, depending) +import System.IO.Unsafe (unsafePerformIO) + + +type De a = (Eq a, Typeable a, Show a) {-| @@ -31,19 +39,20 @@ import System.IO.Unsafe prop> memo = id -} class Memo f where memo :: f -> f -instance Eq a => Memo (a -> b) where memo = memo1 -instance {-# OVERLAPS #-} (Eq a, Eq b) => Memo (a -> b -> c) where memo = memo2 -instance {-# OVERLAPS #-} (Eq a, Eq b, Eq c) => Memo (a -> b -> c -> d) where memo = memo3 -instance {-# OVERLAPS #-} (Eq a, Eq b, Eq c, Eq d) => Memo (a -> b -> c -> d -> e) where memo = memo4 -instance {-# OVERLAPS #-} (Eq a, Eq b, Eq c, Eq d, Eq e) => Memo (a -> b -> c -> d -> e -> f) where memo = memo5 -instance {-# OVERLAPS #-} (Eq a, Eq b, Eq c, Eq d, Eq e, Eq f) => Memo (a -> b -> c -> d -> e -> f -> g) where memo = memo6 -instance {-# OVERLAPS #-} (Eq a, Eq b, Eq c, Eq d, Eq e, Eq f, Eq g) => Memo (a -> b -> c -> d -> e -> f -> g -> h) where memo = memo7 -instance {-# OVERLAPS #-} (Eq a, Eq b, Eq c, Eq d, Eq e, Eq f, Eq g, Eq h) => Memo (a -> b -> c -> d -> e -> f -> g -> h -> i) where memo = memo8 -instance {-# OVERLAPS #-} (Eq a, Eq b, Eq c, Eq d, Eq e, Eq f, Eq g, Eq h, Eq i) => Memo (a -> b -> c -> d -> e -> f -> g -> h -> i -> j) where memo = memo9 - -memo1' e f a = unsafePerformIO $ do - r <- newIORef (a, f a) - return $ applyEq e f r a +instance De a => Memo (a -> Html m b) where memo = memo1 +instance {-# OVERLAPS #-} (De a, De b) => Memo (a -> b -> Html m c) where memo = memo2 +instance {-# OVERLAPS #-} (De a, De b, De c) => Memo (a -> b -> c -> Html m d) where memo = memo3 +instance {-# OVERLAPS #-} (De a, De b, De c, De d) => Memo (a -> b -> c -> d -> Html m e) where memo = memo4 +instance {-# OVERLAPS #-} (De a, De b, De c, De d, De e) => Memo (a -> b -> c -> d -> e -> Html m f) where memo = memo5 +instance {-# OVERLAPS #-} (De a, De b, De c, De d, De e, De f) => Memo (a -> b -> c -> d -> e -> f -> Html m g) where memo = memo6 +instance {-# OVERLAPS #-} (De a, De b, De c, De d, De e, De f, De g) => Memo (a -> b -> c -> d -> e -> f -> g -> Html m h) where memo = memo7 +instance {-# OVERLAPS #-} (De a, De b, De c, De d, De e, De f, De g, De h) => Memo (a -> b -> c -> d -> e -> f -> g -> h -> Html m i) where memo = memo8 +instance {-# OVERLAPS #-} (De a, De b, De c, De d, De e, De f, De g, De h, De i) => Memo (a -> b -> c -> d -> e -> f -> g -> h -> i -> Html m j) where memo = memo9 + +memo1' e f = unsafePerformIO $ do + r <- newIORef Nothing + return $ applyEq e f r +{-# NOINLINE memo1' #-} memo2' e f a b = memo1' e (uncurry f) (a,b) memo3' e f a b c = memo1' e (uncurry2 f) (a,b,c) memo4' e f a b c d = memo1' e (uncurry3 f) (a,b,c,d) @@ -53,25 +62,25 @@ memo7' e f a b c d g h i = memo1' e (uncurry6 f) (a,b,c,d,g,h,i) memo8' e f a b c d g h i j = memo1' e (uncurry7 f) (a,b,c,d,g,h,i,j) memo9' e f a b c d g h i j k = memo1' e (uncurry8 f) (a,b,c,d,g,h,i,j,k) -memo1 :: Eq a => (a -> b) -> a -> b -memo2 :: Eq a => Eq b => (a -> b -> c) -> a -> b -> c -memo3 :: Eq a => Eq b => Eq c => (a -> b -> c -> d) -> a -> b -> c -> d -memo4 :: Eq a => Eq b => Eq c => Eq d => (a -> b -> c -> d -> e) -> a -> b -> c -> d -> e -memo5 :: Eq a => Eq b => Eq c => Eq d => Eq e => (a -> b -> c -> d -> e -> f) -> a -> b -> c -> d -> e -> f -memo6 :: Eq a => Eq b => Eq c => Eq d => Eq e => Eq f => (a -> b -> c -> d -> e -> f -> g) -> a -> b -> c -> d -> e -> f -> g -memo7 :: Eq a => Eq b => Eq c => Eq d => Eq e => Eq f => Eq g => (a -> b -> c -> d -> e -> f -> g -> h) -> a -> b -> c -> d -> e -> f -> g -> h -memo8 :: Eq a => Eq b => Eq c => Eq d => Eq e => Eq f => Eq g => Eq h => (a -> b -> c -> d -> e -> f -> g -> h -> i) -> a -> b -> c -> d -> e -> f -> g -> h -> i -memo9 :: Eq a => Eq b => Eq c => Eq d => Eq e => Eq f => Eq g => Eq h => Eq i => (a -> b -> c -> d -> e -> f -> g -> h -> i -> j) -> a -> b -> c -> d -> e -> f -> g -> h -> i -> j - -memo1 = memo1' (/=) -memo2 = memo2' (/=) -memo3 = memo3' (/=) -memo4 = memo4' (/=) -memo5 = memo5' (/=) -memo6 = memo6' (/=) -memo7 = memo7' (/=) -memo8 = memo8' (/=) -memo9 = memo9' (/=) +memo1 :: De a => (a -> Html m b) -> a -> Html m b +memo2 :: De a => De b => (a -> b -> Html m c) -> a -> b -> Html m c +memo3 :: De a => De b => De c => (a -> b -> c -> Html m d) -> a -> b -> c -> Html m d +memo4 :: De a => De b => De c => De d => (a -> b -> c -> d -> Html m e) -> a -> b -> c -> d -> Html m e +memo5 :: De a => De b => De c => De d => De e => (a -> b -> c -> d -> e -> Html m f) -> a -> b -> c -> d -> e -> Html m f +memo6 :: De a => De b => De c => De d => De e => De f => (a -> b -> c -> d -> e -> f -> Html m g) -> a -> b -> c -> d -> e -> f -> Html m g +memo7 :: De a => De b => De c => De d => De e => De f => De g => (a -> b -> c -> d -> e -> f -> g -> Html m h) -> a -> b -> c -> d -> e -> f -> g -> Html m h +memo8 :: De a => De b => De c => De d => De e => De f => De g => De h => (a -> b -> c -> d -> e -> f -> g -> h -> Html m i) -> a -> b -> c -> d -> e -> f -> g -> h -> Html m i +memo9 :: De a => De b => De c => De d => De e => De f => De g => De h => De i => (a -> b -> c -> d -> e -> f -> g -> h -> i -> Html m j) -> a -> b -> c -> d -> e -> f -> g -> h -> i -> Html m j + +memo1 = memo1' (==) +memo2 = memo2' (==) +memo3 = memo3' (==) +memo4 = memo4' (==) +memo5 = memo5' (==) +memo6 = memo6' (==) +memo7 = memo7' (==) +memo8 = memo8' (==) +memo9 = memo9' (==) uncurry2 f (a,b,c) = f a b c uncurry3 f (a,b,c,d) = f a b c d @@ -81,11 +90,8 @@ uncurry6 f (a,b,c,d,e,g,h) = f a b c d e g h uncurry7 f (a,b,c,d,e,g,h,i) = f a b c d e g h i uncurry8 f (a,b,c,d,e,g,h,i,j) = f a b c d e g h i j -applyEq :: (a -> a -> Bool) -> (a -> b) -> IORef (a, b) -> a -> b -applyEq e f r a = unsafePerformIO $ do - (a', b) <- readIORef r - if not $ e a' a then return b else do - let b' = f a - writeIORef r (a', b') - return b' - +applyEq :: (Typeable a, Eq a, Show a) => (a -> a -> Bool) -> (a -> Html m b) -> IORef (Maybe (a, Html m b)) -> a -> Html m b +applyEq e f r a = unsafePerformIO $ readIORef r >>= \case + Just (a', b) | e a' a -> pure b + _ -> let b = depending f a in b <$ writeIORef r (Just (a, b)) +{-# NOINLINE applyEq #-} diff --git a/html/package.yaml b/html/package.yaml index 842f3599dfe995da0e70ac55a886910ee36b0a93..512099ad3d998de76801d168535cb604be0afc2b 100644 --- a/html/package.yaml +++ b/html/package.yaml @@ -46,6 +46,7 @@ dependencies: - lens - Shpadoinkle + - Shpadoinkle-console when: diff --git a/nix/hpackall.sh b/nix/hpackall.sh index 560c5f7745ae3ec5b7489f50192c6ec8c2586d17..906451f18aadb9ba2393b4c7b06c0897cbca22c1 100755 --- a/nix/hpackall.sh +++ b/nix/hpackall.sh @@ -16,3 +16,4 @@ p widgets p examples p tests p isreal +p streaming diff --git a/nix/overlay-shpadoinkle.nix b/nix/overlay-shpadoinkle.nix index 8b82ff13b0c0da9c747856ab12e31bcfcfc63ac2..06f0ef9dd1a2fcc9a74059915869b50a065918ec 100644 --- a/nix/overlay-shpadoinkle.nix +++ b/nix/overlay-shpadoinkle.nix @@ -160,6 +160,7 @@ in { Shpadoinkle-marketing = call "Shpadoinkle-marketing" ../marketing; Shpadoinkle-html = call "Shpadoinkle-html" ../html; Shpadoinkle-router = call "Shpadoinkle-router" ../router; + Shpadoinkle-streaming = call "Shpadoinkle-streaming" ../streaming; Shpadoinkle-widgets = addTest (call "Shpadoinkle-widgets" ../widgets) hpkgs; Shpadoinkle-tests = super.haskell.packages.${compiler}.callCabal2nix "tests" (gitignore ../tests) {}; diff --git a/nix/overlay.nix b/nix/overlay.nix index cd1439fabb5a1e9839e8ee722afef8990eee8034..569ac2e57798244ce1d52cf9c759f0f7fcf9bef3 100644 --- a/nix/overlay.nix +++ b/nix/overlay.nix @@ -1,7 +1,7 @@ -{ chan, compiler, isJS }: +{ chan, compiler, isJS, enableLibraryProfiling, enableExecutableProfiling }: with (import ./base-pkgs.nix { inherit chan; } {}).lib; foldl' composeExtensions (_: _: {}) [ - (import ./overlay-reflex.nix { inherit compiler isJS; }) + (import ./overlay-reflex.nix { inherit compiler isJS enableLibraryProfiling; }) (import ./overlay-shpadoinkle.nix { inherit compiler isJS; }) ] diff --git a/nix/pkgs.nix b/nix/pkgs.nix index e5a22a0ef002bc4936daf4d97a67333807bbf0e6..54df24278340dd3ce46e32aa4d4a204261b2873f 100644 --- a/nix/pkgs.nix +++ b/nix/pkgs.nix @@ -2,10 +2,12 @@ , isJS ? false , system ? "x86_64-linux" , chan ? (import ./chan.nix) +, enableLibraryProfiling ? false +, enableExecutableProfiling ? false }: import ./base-pkgs.nix { inherit chan; } { inherit system; overlays = [ - (import ./overlay.nix { inherit chan compiler isJS; }) + (import ./overlay.nix { inherit chan compiler isJS enableLibraryProfiling enableExecutableProfiling; }) ]; } diff --git a/streaming/LICENSE b/streaming/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..e0066cfb7cd61f2d21a37d8feece3358a5fd8129 --- /dev/null +++ b/streaming/LICENSE @@ -0,0 +1,27 @@ +Shpadoinkle Streaming aka S11 Streaming +Copyright © 2021 Morgan Thomas +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Shpadoinkle nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/streaming/Setup.hs b/streaming/Setup.hs new file mode 100644 index 0000000000000000000000000000000000000000..44671092b28be990b6340bb828923fe3a19ae773 --- /dev/null +++ b/streaming/Setup.hs @@ -0,0 +1,2 @@ +import Distribution.Simple +main = defaultMain diff --git a/streaming/Shpadoinkle/Streaming.hs b/streaming/Shpadoinkle/Streaming.hs new file mode 100644 index 0000000000000000000000000000000000000000..0e74491494198a40ae18ac065974fda5c5e51a53 --- /dev/null +++ b/streaming/Shpadoinkle/Streaming.hs @@ -0,0 +1,28 @@ +{-# LANGUAGE ScopedTypeVariables #-} + + +module Shpadoinkle.Streaming + ( consumeStream + ) where + + +import Shpadoinkle hiding (h) + +import Data.Functor.Of (Of ((:>))) +import Streaming (Stream) +import Streaming.Internal (destroy) + + +consumeStream :: forall m a b. Monad m => Stream (Of a) m () -> (a -> m (b -> b)) -> Continuation m b +consumeStream stream f = destroy stream g h j + where + g :: Of a (Continuation m b) -> Continuation m b + g (a :> k) = voidRunContinuationT $ do + commit (impur (f a)) + commit (merge k) + + h :: m (Continuation m b) -> Continuation m b + h = causedBy + + j :: () -> Continuation m b + j = const done diff --git a/streaming/default.nix b/streaming/default.nix new file mode 100644 index 0000000000000000000000000000000000000000..c7b5bb88042376313da290a47f488be514ca4239 --- /dev/null +++ b/streaming/default.nix @@ -0,0 +1,4 @@ +import ../default.nix { pack = "Shpadoinkle-streaming"; } + + + diff --git a/streaming/package.yaml b/streaming/package.yaml new file mode 100644 index 0000000000000000000000000000000000000000..c158cdd5c455210769f5c9448beb8f9dc04000b7 --- /dev/null +++ b/streaming/package.yaml @@ -0,0 +1,34 @@ +name: Shpadoinkle-streaming +license: BSD3 +license-file: LICENSE +version: 0.0.0.1 +author: Morgan Thomas +maintainer: morgan.a.s.thomas@gmail.com +category: Web +build-type: Simple +synopsis: Integration of the streaming library with Shpadoinkle continuations. +description: + Integration of the streaming library with Shpadoinkle continuations. + + +ghc-options: + - -Wall + - -Wcompat + - -fwarn-redundant-constraints + - -fwarn-tabs + - -fwarn-incomplete-record-updates + - -fwarn-identities + + +dependencies: + - base >= 4.12.0 && < 4.16 + - lens + - streaming >= 0.2 && < 0.3 + - text + + - Shpadoinkle + + +library: + exposed-modules: Shpadoinkle.Streaming + source-dirs: .