{-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# OPTIONS_HADDOCK prune #-} {- | Effect systems are being actively explored as ways to structure Haskell programs. This package provides experimental support for the __effectful__ effect system. This module introcuces a new effect, 'ProgramE', which plumbs the current program context into the effect system. By calling 'runProgramE' to add the 'ProgramE' effect to the current list of in-scope effects you can then use 'withProgram'' to run a 'Program' action. The more general 'withProgram' gives you an unlifting function to return back to the effect system so you can continue processing within your effects stack. = Usage As an example, here's an effect which lifts to 'Program' @τ@, does some log output, then unlifts back to 'Effectful.Internal.Monad.Eff' @es@ to then query something from the 'Effectful.Environment.Environment' effect (which requires the 'Effectful.Internal.Monad.IOE' effect to run): @ retrieveProgramName :: forall τ es . ('Effectful.Internal.Monad.IOE' 'Effectful.Internal.Effect.:>' es, 'ProgramE' τ 'Effectful.Internal.Effect.:>' es) => 'Effectful.Internal.Monad.Eff' es () retrieveProgramName = do -- we're in (IOE :> es, ProgramE :> es) => Eff es, right? 'withProgram' @τ $ \\runEffect -> do -- now we're in Program τ 'Core.Program.Logging.info' \"Running in Program\" path <- runEffect $ do -- now back in (IOE :> es, ProgramE τ :> es) => Eff es, and can call -- something that requires the IOE effect be present. 'Effectful.Environment.runEnvironment' $ do -- now in (Environment :> es) => Eff es 'Effectful.Environment.getExecutablePath' 'Core.Program.Logging.info' \"Done running effects\" 'Core.Program.Logging.debugS' "path" path @ The @@τ@ type application shown here is vital; without it the compiler will not be able to resolve all the ambiguous types when attempting to determine which effect to run. It doesn't have to be polymorphic; if you know the actual top-level application state type you can do @@Settings@ or whatever. This all assumes you are running with the 'ProgramE' @τ@ effect in-scope. You can achieve that as follows: @ main :: 'IO' () main = 'Core.Program.Execute.execute' program program :: 'Program' 'None' () program = do -- in Program τ, where τ is None here context <- 'Core.Program.Execute.getContext' 'Control.Monad.IO.Class.liftIO' $ do -- in IO 'Effectful.Internal.Monad.runEff' $ do -- in (IOE :> es) => Eff es 'runProgramE' context $ do -- in (IOE :> es, ProgramE τ :> es) => Eff es ... @ -} module Core.Effect.Effectful ( -- * Effect ProgramE , runProgramE -- * Lifting and unlifting , withProgram , withProgram' ) where -- -- We follow the convention used elsewhere in this collection of libraries of -- using a qualified name for the imports of significant libraries. It's a bit -- cumbersome, but makes it easier to disambiguate what's going on when -- comparing to almost identical code in sibling modules covering other -- webserver frameworks. -- import Core.Program.Context import Core.System.Base import Data.Kind (Type) import Effectful.Internal.Effect (type (:>)) import Effectful.Internal.Effect qualified as Effect ( Effect ) import Effectful.Internal.Env qualified as Effect ( Dispatch (Static) , DispatchOf , SideEffects (WithSideEffects) ) import Effectful.Internal.Monad qualified as Effect ( Eff , IOE , StaticRep , evalStaticRep , getStaticRep , putStaticRep , withEffToIO ) ------------------------------------------------------------------------------- -- Effect {- | An effect giving you access to return to the 'Program' @τ@ monad. -} data ProgramE (τ :: Type) :: Effect.Effect type instance Effect.DispatchOf (ProgramE τ) = 'Effect.Static 'Effect.WithSideEffects newtype instance Effect.StaticRep (ProgramE τ) = ProgramE (Context τ) ------------------------------------------------------------------------------- -- Interpretation {- | Given you are in the 'Effectful.Internal.Monad.IOE' effect, raise the currently in-scope effects to include the 'ProgramE' effect. This will presumably be invoked fairly soon after entering the effect system, and it needs to have been done inside a program that was started with 'Core.Program.Execute.execute' or 'Core.Program.Execute.executeWith'. Assuming that to be the case, get the 'Context' @τ@ object from the outside edge of your program using 'getContext' and then provide it to this function at the earliest opportunity. -} runProgramE :: forall τ es α . Effect.IOE :> es => Context τ -> Effect.Eff (ProgramE τ : es) α -> Effect.Eff es α runProgramE context = Effect.evalStaticRep (ProgramE context) -------------------------------------------------------------------------------- -- Wrappers {- | Simple variant of 'withProgram' which allows you to run a 'Program' @τ@ monad action from within the effect system, provided that the 'ProgramE' @τ@ effect is in scope. -} withProgram' :: forall τ es α . (Effect.IOE :> es, ProgramE τ :> es) => Program τ α -> Effect.Eff es α withProgram' action = do ProgramE context1 <- Effect.getStaticRep -- lift to IO liftIO $ do -- now in IO. Lift to Program τ subProgram context1 $ do action {- | Run a 'Program' @τ@ monad action within the 'ProgramE' @τ@ effect. This allows you the ability to lift to the 'Program' @τ@ monad, giving you the ability to run actions that do logging, telemetry, input/output, and exception handling, and then unlift back to the 'Eff' @es@ effect to continue work in the effects system. The order of the existential types in the @forall@ turned out to matter; it allows you to use the @TypeApplications@ language extention to resolve the ambiguous types when invoking this function. See also "Core.Program.Unlift" for a general discussion of the unlifting problem and in particular the 'Core.Program.Unlift.withContext' for a function with a comparable type signature. -} withProgram :: forall τ es α . (Effect.IOE :> es, ProgramE τ :> es) => ((forall β. Effect.Eff es β -> Program τ β) -> Program τ α) -> Effect.Eff es α withProgram action = do -- extract Context τ ProgramE context1 <- Effect.getStaticRep -- lift to IO, using the provided specialized function which gives an unlift -- function for later use. Effect.withEffToIO $ \runInIO -> -- now in IO. Lift to Program τ subProgram context1 $ do -- now in Program τ. We form the function that will run an effect -- in Program τ, and pass it to the supplied action. action $ \inner -> do context2 <- getContext liftIO $ do -- now in IO runInIO $ do -- now in (IOE :> es, ProgramE :> es) => Eff es, but -- we need to update the Context τ in the ProgramE τ -- effect with the one that was present just before we -- ran the unlifting function. Effect.putStaticRep (ProgramE context2) -- now we can proceed with running the nested effects. inner