{-# LANGUAGE BangPatterns, TemplateHaskell, TupleSections #-}
{-# OPTIONS_HADDOCK not-home #-}
module Polysemy.Internal.Writer where
import Control.Concurrent.STM
import Control.Exception
import Control.Monad
import qualified Control.Monad.Trans.Writer.Lazy as Lazy
import Data.Bifunctor (first)
import Data.Semigroup
import Polysemy
import Polysemy.Final
import Polysemy.Internal
import Polysemy.Internal.Union
data Writer o m a where
Tell :: o -> Writer o m ()
Listen :: ∀ o m a. m a -> Writer o m (o, a)
Pass :: m (o -> o, a) -> Writer o m a
makeSem ''Writer
writerToEndoWriter
:: (Monoid o, Member (Writer (Endo o)) r)
=> Sem (Writer o ': r) a
-> Sem r a
writerToEndoWriter = interpretH $ \case
Tell o -> tell (Endo (o <>)) >>= pureT
Listen m -> do
m' <- writerToEndoWriter <$> runT m
raise $ do
(o, fa) <- listen m'
return $ (,) (appEndo o mempty) <$> fa
Pass m -> do
ins <- getInspectorT
m' <- writerToEndoWriter <$> runT m
raise $ pass $ do
t <- m'
let
f' =
maybe
id
(\(f, _) (Endo oo) -> let !o' = f (oo mempty) in Endo (o' <>))
(inspect ins t)
return (f', fmap snd t)
{-# INLINE writerToEndoWriter #-}
runWriterSTMAction :: forall o r a
. (Member (Final IO) r, Monoid o)
=> (o -> STM ())
-> Sem (Writer o ': r) a
-> Sem r a
runWriterSTMAction write = interpretH $ \case
Tell o -> do
t <- embedFinal $ atomically (write o)
pureT t
Listen m -> do
m' <- runT m
raise $ withWeavingToFinal $ \s wv _ -> mask $ \restore -> do
tvar <- newTVarIO mempty
switch <- newTVarIO False
fa <-
restore (wv (runWriterSTMAction (writeListen tvar switch) m' <$ s))
`onException` commitListen tvar switch
o <- commitListen tvar switch
return $ (fmap . fmap) (o, ) fa
Pass m -> do
m' <- runT m
ins <- getInspectorT
raise $ withWeavingToFinal $ \s wv ins' -> mask $ \restore -> do
tvar <- newTVarIO mempty
switch <- newTVarIO False
t <-
restore (wv (runWriterSTMAction (writePass tvar switch) m' <$ s))
`onException` commitPass tvar switch id
commitPass tvar switch
(maybe id fst $ ins' t >>= inspect ins)
return $ (fmap . fmap) snd t
where
writeListen :: TVar o
-> TVar Bool
-> o
-> STM ()
writeListen tvar switch = \o -> do
alreadyCommited <- readTVar switch
unless alreadyCommited $ do
s <- readTVar tvar
writeTVar tvar $! s <> o
write o
{-# INLINE writeListen #-}
writePass :: TVar o
-> TVar Bool
-> o
-> STM ()
writePass tvar switch = \o -> do
useGlobal <- readTVar switch
if useGlobal then
write o
else do
s <- readTVar tvar
writeTVar tvar $! s <> o
{-# INLINE writePass #-}
commitListen :: TVar o
-> TVar Bool
-> IO o
commitListen tvar switch = atomically $ do
writeTVar switch True
readTVar tvar
{-# INLINE commitListen #-}
commitPass :: TVar o
-> TVar Bool
-> (o -> o)
-> IO ()
commitPass tvar switch f = atomically $ do
o <- readTVar tvar
let !o' = f o
alreadyCommited <- readTVar switch
unless alreadyCommited $
write o'
writeTVar switch True
{-# INLINE commitPass #-}
{-# INLINE runWriterSTMAction #-}
interpretViaLazyWriter
:: forall o e r a
. Monoid o
=> (forall m x. Monad m => Weaving e (Lazy.WriterT o m) x -> Lazy.WriterT o m x)
-> Sem (e ': r) a
-> Sem r (o, a)
interpretViaLazyWriter f sem = Sem $ \(k :: forall x. Union r (Sem r) x -> m x) ->
let
go :: forall x. Sem (e ': r) x -> Lazy.WriterT o m x
go = usingSem $ \u -> case decomp u of
Right (Weaving e s wv ex ins) -> f $ Weaving e s (go . wv) ex ins
Left g -> Lazy.WriterT $ do
~(o, a) <- k $
weave
(mempty, ())
(\ ~(o, m) -> (fmap . first) (o <>) (interpretViaLazyWriter f m))
(Just . snd)
g
return (a, o)
{-# INLINE go #-}
in do
~(a,s) <- Lazy.runWriterT (go sem)
return (s, a)
{-# INLINE interpretViaLazyWriter #-}