effectful-core-2.3.0.0: src/Effectful/Provider.hs
-- | Turn an effect handler into an effectful operation.
module Effectful.Provider
( -- * Example
-- $example
-- * Effect
Provider
, Provider_
-- ** Handlers
, runProvider
, runProvider_
-- ** Operations
, provide
, provide_
, provideWith
, provideWith_
) where
import Control.Monad
import Data.Coerce
import Data.Functor.Identity
import Data.Kind (Type)
import Data.Primitive.PrimArray
import Effectful
import Effectful.Dispatch.Static
import Effectful.Dispatch.Static.Primitive
import Effectful.Internal.Env (Env(..))
import Effectful.Internal.Utils
-- $example
--
-- >>> import Control.Monad.IO.Class
-- >>> import Effectful.Dispatch.Dynamic
-- >>> import Effectful.State.Static.Local
-- >>> import qualified Data.Map.Strict as M
--
-- Given an effect:
--
-- >>> :{
-- data Write :: Effect where
-- Write :: String -> Write m ()
-- type instance DispatchOf Write = Dynamic
-- :}
--
-- >>> :{
-- write :: Write :> es => String -> Eff es ()
-- write = send . Write
-- :}
--
-- its handler can be turned into an effectful operation with the 'Provider'
-- effect:
--
-- >>> :{
-- action :: Provider_ Write FilePath :> es => Eff es ()
-- action = do
-- provideWith_ @Write "in.txt" $ do
-- write "hi"
-- write "there"
-- provideWith_ @Write "out.txt" $ do
-- write "good"
-- write "bye"
-- :}
--
-- Then, given multiple interpreters:
--
-- >>> :{
-- runWriteIO
-- :: IOE :> es
-- => FilePath
-- -> Eff (Write : es) a
-- -> Eff es a
-- runWriteIO fp = interpret $ \_ -> \case
-- Write msg -> liftIO . putStrLn $ fp ++ ": " ++ msg
-- :}
--
-- >>> :{
-- runWritePure
-- :: State (M.Map FilePath [String]) :> es
-- => FilePath
-- -> Eff (Write : es) a
-- -> Eff es a
-- runWritePure fp = interpret $ \_ -> \case
-- Write msg -> modify $ M.insertWith (++) fp [msg]
-- :}
--
-- @action@ can be supplied with either of them for the appropriate behavior:
--
-- >>> :{
-- runEff
-- . runProvider_ runWriteIO
-- $ action
-- :}
-- in.txt: hi
-- in.txt: there
-- out.txt: good
-- out.txt: bye
--
-- >>> :{
-- runPureEff
-- . fmap (fmap reverse)
-- . execState @(M.Map FilePath [String]) M.empty
-- . runProvider_ runWritePure
-- $ action
-- :}
-- fromList [("in.txt",["hi","there"]),("out.txt",["good","bye"])]
-- | Provide a way to run a handler of @e@ with a given @input@.
--
-- /Note:/ @f@ can be used to alter the return type of the effect handler. If
-- that's unnecessary, use 'Provider_'.
data Provider (e :: Effect) (input :: Type) (f :: Type -> Type) :: Effect
-- | A restricted variant of 'Provider' with unchanged return type of the effect
-- handler.
type Provider_ e input = Provider e input Identity
type instance DispatchOf (Provider e input f) = Static NoSideEffects
data instance StaticRep (Provider e input f) where
Provider :: !(Env handlerEs)
-> !(forall r. input -> Eff (e : handlerEs) r -> Eff handlerEs (f r))
-> StaticRep (Provider e input f)
-- | Run the 'Provider' effect with a given effect handler.
runProvider
:: (forall r. input -> Eff (e : es) r -> Eff es (f r))
-- ^ The effect handler.
-> Eff (Provider e input f : es) a
-> Eff es a
runProvider run m = unsafeEff $ \es0 -> do
inlineBracket
(consEnv (Provider es0 run) relinkProvider es0)
unconsEnv
(\es -> unEff m es)
-- | Run the 'Provider' effect with a given effect handler that doesn't change
-- its return type.
runProvider_
:: (forall r. input -> Eff (e : es) r -> Eff es r)
-- ^ The effect handler.
-> Eff (Provider_ e input : es) a
-> Eff es a
runProvider_ run = runProvider $ \input -> coerce . run input
-- | Run the effect handler.
provide :: Provider e () f :> es => Eff (e : es) a -> Eff es (f a)
provide = provideWith ()
-- | Run the effect handler with unchanged return type.
provide_ :: Provider_ e () :> es => Eff (e : es) a -> Eff es a
provide_ = provideWith_ ()
-- | Run the effect handler with a given input.
provideWith
:: Provider e input f :> es
=> input
-- ^ The input to the effect handler.
-> Eff (e : es) a
-> Eff es (f a)
provideWith input action = unsafeEff $ \es -> do
Provider handlerEs run <- getEnv es
(`unEff` handlerEs) . run input . unsafeEff $ \eHandlerEs -> do
unEff action =<< copyRef eHandlerEs es
-- | Run the effect handler that doesn't change its return type with a given
-- input.
provideWith_
:: Provider_ e input :> es
=> input
-- ^ The input to the effect handler.
-> Eff (e : es) a
-> Eff es a
provideWith_ input = adapt . provideWith input
where
adapt :: Eff es (Identity a) -> Eff es a
adapt = coerce
----------------------------------------
-- Helpers
relinkProvider :: Relinker StaticRep (Provider e input f)
relinkProvider = Relinker $ \relink (Provider handlerEs run) -> do
newHandlerEs <- relink handlerEs
pure $ Provider newHandlerEs run
copyRef :: Env (e : handlerEs) -> Env es -> IO (Env (e : es))
copyRef (Env hoffset hrefs hstorage) (Env offset refs0 storage) = do
when (hstorage /= storage) $ do
error "storages do not match"
let size = sizeofPrimArray refs0 - offset
mrefs <- newPrimArray (size + 2)
copyPrimArray mrefs 2 refs0 offset size
writePrimArray mrefs 0 $ indexPrimArray hrefs hoffset
writePrimArray mrefs 1 $ indexPrimArray hrefs (hoffset + 1)
refs <- unsafeFreezePrimArray mrefs
pure $ Env 0 refs storage