{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeFamilies #-}
{-# OPTIONS_GHC -Wno-deferred-type-errors #-}
module Shpadoinkle.Continuation (
Continuation (..)
, runContinuation
, done, pur, impur, kleisli, causes, causedBy, merge, contIso, before, after
, Continuous (..)
, hoist
, voidC', voidC, forgetC
, liftC', liftCMay', liftC, liftCMay
, leftC', leftC, rightC', rightC
, eitherC', eitherC
, maybeC', maybeC, comaybe, comaybeC', comaybeC
, writeUpdate, shouldUpdate, constUpdate
, ContinuationT (..), voidRunContinuationT, kleisliT, commit
, module Control.DeepSeq
) where
import Control.Arrow (first)
import Control.DeepSeq (NFData (..), force)
import Control.Monad (void)
import Control.Monad.Trans.Class (MonadTrans (..))
import Data.Foldable (traverse_)
import Data.Maybe (fromMaybe)
import GHC.Conc (retry)
import GHCJS.DOM (currentWindowUnchecked)
import GHCJS.DOM.Window (Window)
import GHCJS.DOM.WindowOrWorkerGlobalScope (clearTimeout, setTimeout)
import Language.Javascript.JSaddle (MonadJSM, fun, JSM)
import UnliftIO (MonadUnliftIO, TVar,
UnliftIO, askUnliftIO,
atomically, liftIO,
newTVarIO, readTVar,
readTVarIO, unliftIO,
writeTVar)
import UnliftIO.Concurrent (forkIO)
data Continuation m a = Continuation (a -> a) (a -> m (Continuation m a))
| Rollback (Continuation m a)
| Merge (Continuation m a)
| Pure (a -> a)
pur :: (a -> a) -> Continuation m a
pur = Pure
done :: Continuation m a
done = pur id
{-# SPECIALIZE impur :: JSM (a -> a) -> Continuation JSM a #-}
impur :: Applicative m => m (a -> a) -> Continuation m a
impur m = kleisli . const $ (\f -> Continuation f (const (pure done))) <$> m
kleisli :: (a -> m (Continuation m a)) -> Continuation m a
kleisli = Continuation id
{-# SPECIALIZE causes :: JSM () -> Continuation JSM a #-}
causes :: Applicative m => m () -> Continuation m a
causes m = impur (id <$ m)
causedBy :: m (Continuation m a) -> Continuation m a
causedBy = Continuation id . const
merge :: Continuation m a -> Continuation m a
merge = Merge
before :: Applicative m => Continuation m a -> Continuation m a -> Continuation m a
Pure f `before` Continuation g h = Continuation (g . f) h
Pure _ `before` Rollback g = g
Pure f `before` Merge g = Continuation f (const (pure (Merge g)))
Pure f `before` Pure g = Pure (g.f)
Merge f `before` g = Merge (f `before` g)
Rollback f `before` g = Rollback (f `before` g)
Continuation f g `before` h = Continuation f $ fmap (`before` h) . g
after :: Applicative m => Continuation m a -> Continuation m a -> Continuation m a
after = flip before
{-# SPECIALIZE runContinuation :: Continuation JSM a -> a -> JSM (a -> a) #-}
runContinuation :: Monad m => Continuation m a -> a -> m (a -> a)
runContinuation = runContinuation' id
{-# SPECIALIZE runContinuation' :: (a -> a) -> Continuation JSM a -> a -> JSM (a -> a) #-}
runContinuation' :: Monad m => (a -> a) -> Continuation m a -> a -> m (a -> a)
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)
class Continuous f where
mapC :: (Continuation m a -> Continuation m b) -> f m a -> f m b
instance Continuous Continuation where
mapC = id
{-# SPECIALIZE hoist :: (forall b. JSM b -> n b) -> Continuation JSM a -> Continuation n a #-}
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
{-# SPECIALIZE liftC' :: (a -> b -> b) -> (b -> a) -> Continuation JSM a -> Continuation JSM b #-}
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 (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))
{-# SPECIALIZE liftCMay' :: (a -> b -> b) -> (b -> Maybe a) -> Continuation JSM a -> Continuation JSM b #-}
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)
{-# SPECIALIZE liftC :: Functor m => (a -> b -> b) -> (b -> a) -> Continuation m a -> Continuation m b #-}
liftC :: Functor m => Continuous f => (a -> b -> b) -> (b -> a) -> f m a -> f m b
liftC f g = mapC (liftC' f g)
{-# SPECIALIZE liftCMay :: Applicative m => (a -> b -> b) -> (b -> Maybe a) -> Continuation m a -> Continuation m b #-}
liftCMay :: Applicative m => Continuous f => (a -> b -> b) -> (b -> Maybe a) -> f m a -> f m b
liftCMay f g = mapC (liftCMay' f g)
{-# SPECIALIZE voidC' :: Continuation JSM () -> Continuation JSM a #-}
voidC' :: Monad m => Continuation m () -> Continuation m a
voidC' f = Continuation id $ \_ -> do
_ <- runContinuation f ()
return done
{-# SPECIALIZE voidC :: Monad m => Continuation m () -> Continuation m a #-}
{-# SPECIALIZE voidC :: Continuation JSM () -> Continuation JSM a #-}
voidC :: Monad m => Continuous f => f m () -> f m a
voidC = mapC voidC'
{-# SPECIALIZE forgetC :: Continuation m a -> Continuation m b #-}
{-# SPECIALIZE forgetC :: Continuation JSM a -> Continuation JSM b #-}
forgetC :: Continuous f => f m a -> f m b
forgetC = mapC (const done)
{-# SPECIALIZE leftC' :: Continuation JSM a -> Continuation JSM (a,b) #-}
leftC' :: Functor m => Continuation m a -> Continuation m (a,b)
leftC' = liftC' (\x (_,y) -> (x,y)) fst
{-# SPECIALIZE leftC :: Continuation JSM a -> Continuation JSM (a,b) #-}
leftC :: Functor m => Continuous f => f m a -> f m (a,b)
leftC = mapC leftC'
{-# SPECIALIZE rightC' :: Continuation JSM b -> Continuation JSM (a,b) #-}
rightC' :: Functor m => Continuation m b -> Continuation m (a,b)
rightC' = liftC' (\y (x,_) -> (x,y)) snd
{-# SPECIALIZE rightC :: Continuation JSM b -> Continuation JSM (a,b) #-}
rightC :: Functor m => Continuous f => f m b -> f m (a,b)
rightC = mapC rightC'
{-# SPECIALIZE maybeC' :: Continuation JSM a -> Continuation JSM (Maybe a) #-}
maybeC' :: Applicative m => Continuation m a -> Continuation m (Maybe a)
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
Nothing -> pure (Rollback done)
{-# SPECIALIZE maybeC' :: Continuation JSM a -> Continuation JSM (Maybe a) #-}
maybeC :: Applicative m => Continuous f => f m a -> f m (Maybe a)
maybeC = mapC maybeC'
comaybe :: (Maybe a -> Maybe a) -> (a -> a)
comaybe f x = fromMaybe x . f $ Just x
{-# SPECIALIZE comaybeC' :: Continuation JSM (Maybe a) -> Continuation JSM a #-}
comaybeC' :: Functor m => Continuation m (Maybe a) -> Continuation m a
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)
{-# SPECIALIZE comaybeC :: Continuation JSM (Maybe a) -> Continuation JSM a #-}
comaybeC :: Functor m => Continuous f => f m (Maybe a) -> f m a
comaybeC = mapC comaybeC'
mapLeft :: (a -> b) -> Either a c -> Either b c
mapLeft f (Left x) = Left (f x)
mapLeft _ (Right x) = Right x
mapRight :: (b -> c) -> Either a b -> Either a c
mapRight _ (Left x) = Left x
mapRight f (Right x) = Right (f x)
{-# SPECIALIZE eitherC' :: Continuation JSM a -> Continuation JSM b -> Continuation JSM (Either a b) #-}
eitherC' :: Applicative m => Continuation m a -> Continuation m b -> Continuation m (Either a b)
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
{-# SPECIALIZE eitherC :: (a -> Continuation JSM a) -> (b -> Continuation JSM b) -> Either a b -> Continuation JSM (Either a b) #-}
eitherC :: Applicative m => Continuous f => (a -> f m a) -> (b -> f m b) -> Either a b -> f m (Either a b)
eitherC l _ (Left x) = mapC (\c -> eitherC' c (pur id)) (l x)
eitherC _ r (Right x) = mapC (eitherC' (pur id)) (r x)
{-# SPECIALIZE contIso :: (a -> b) -> (b -> a) -> Continuation JSM a -> Continuation JSM b #-}
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 (Merge h) = Merge (contIso f g h)
contIso f g (Pure h) = Pure (f.h.g)
instance Applicative m => Semigroup (Continuation m a) where
(Continuation f g) <> (Continuation h i) =
Continuation (f.h) (\x -> (<>) <$> g x <*> i x)
(Continuation f g) <> (Rollback h) =
Rollback (Continuation f (fmap (<> h) . g))
(Rollback h) <> (Continuation _ g) =
Rollback (Continuation id (fmap (h <>) . g))
(Rollback f) <> (Rollback g) = Rollback (f <> g)
(Pure f) <> (Pure g) = Pure (f.g)
(Pure f) <> (Continuation g h) = Continuation (f.g) h
(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)
instance Applicative m => Monoid (Continuation m a) where
mempty = done
{-# SPECIALIZE writeUpdate' :: NFData a => (a -> a) -> TVar a -> (a -> JSM (Continuation JSM a)) -> JSM () #-}
writeUpdate' :: MonadUnliftIO m => NFData a => (a -> a) -> TVar a -> (a -> m (Continuation m a)) -> m ()
writeUpdate' h model f = do
i <- readTVarIO model
m <- f (h i)
case m of
Continuation g gs -> writeUpdate' (g . h) model 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))
{-# SPECIALIZE writeUpdate :: NFData a => TVar a -> Continuation JSM a -> JSM () #-}
writeUpdate :: MonadUnliftIO m => NFData a => TVar a -> Continuation m a -> m ()
writeUpdate model = \case
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
{-# SPECIALIZE shouldUpdate :: forall a b. Eq a => (b -> a -> JSM b) -> b -> TVar a -> JSM () #-}
shouldUpdate :: forall a b m. MonadJSM m => MonadUnliftIO m => Eq a => (b -> a -> m b) -> b -> TVar a -> m ()
shouldUpdate sun prev currentModel = do
sampleModel :: a <- readTVarIO currentModel
previousModel :: TVar a <- newTVarIO sampleModel
currentState :: TVar b <- newTVarIO prev
window :: Window <- currentWindowUnchecked
context :: UnliftIO m <- askUnliftIO
let
go :: [Int] -> m ()
go frames = do
newModel <- atomically $ do
new' <- readTVar currentModel
old <- readTVar previousModel
if new' == old then retry else new' <$ writeTVar previousModel new'
traverse_ (clearTimeout window . Just) frames
let callback = fun $ \_ _ _ -> do
x <- readTVarIO currentState
y <- liftIO $ unliftIO context $ sun x newModel
atomically $ writeTVar currentState y
traverse_ (clearTimeout window . Just) frames
frameId' <- setTimeout window callback Nothing
go (frameId':frames)
() <$ forkIO (go mempty)
newtype ContinuationT model m a = ContinuationT
{ runContinuationT :: m (a, Continuation m model) }
{-# SPECIALIZE commit :: Continuation JSM model -> ContinuationT model JSM () #-}
commit :: Applicative m => Continuation m model -> ContinuationT model m ()
commit = ContinuationT . pure . ((),)
{-# SPECIALIZE voidRunContinuationT :: ContinuationT model JSM a -> Continuation JSM model #-}
voidRunContinuationT :: Functor m => ContinuationT model m a -> Continuation m model
voidRunContinuationT m = kleisli . const $ snd <$> runContinuationT m
{-# SPECIALIZE kleisliT :: (model -> ContinuationT model JSM a) -> Continuation JSM model #-}
kleisliT :: Applicative m => (model -> ContinuationT model m a) -> Continuation m model
kleisliT f = kleisli (pure . voidRunContinuationT . f)
instance Functor m => Functor (ContinuationT model m) where
fmap f = ContinuationT . fmap (first f) . runContinuationT
instance Applicative m => Applicative (ContinuationT model m) where
pure = ContinuationT . pure . (, done)
ft <*> xt = ContinuationT $ do
(\(f, fc) (x, xc) -> (f x, fc <> xc))
<$> runContinuationT ft
<*> runContinuationT xt
instance Monad m => Monad (ContinuationT model m) where
return = ContinuationT . return . (, done)
m >>= f = ContinuationT $ do
(x, g) <- runContinuationT m
(y, h) <- runContinuationT (f x)
return (y, g `before` h)
instance MonadTrans (ContinuationT model) where
lift = ContinuationT . fmap (, done)
constUpdate :: a -> Continuation m a
constUpdate = pur . const
{-# INLINE constUpdate #-}