core-effect-effectful (empty) → 0.0.0.4
raw patch · 3 files changed
+287/−0 lines, 3 filesdep +basedep +core-datadep +core-program
Dependencies added: base, core-data, core-program, core-text, effectful-core
Files
- LICENSE +19/−0
- core-effect-effectful.cabal +47/−0
- lib/Core/Effect/Effectful.hs +221/−0
+ LICENSE view
@@ -0,0 +1,19 @@+Copyright © 2018-2021 Athae Eredh Siniath and Others++Permission is hereby granted, free of charge, to any person obtaining a+copy of this software and associated documentation files (the "Software"),+to deal in the Software without restriction, including without limitation+the rights to use, copy, modify, merge, publish, distribute, sublicense,+and/or sell copies of the Software, and to permit persons to whom the+Software is furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in+all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER+DEALINGS IN THE SOFTWARE.
+ core-effect-effectful.cabal view
@@ -0,0 +1,47 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.35.1.+--+-- see: https://github.com/sol/hpack++name: core-effect-effectful+version: 0.0.0.4+synopsis: Interoperability with the effectful effects system+description: This is part of a library to help build command-line programs, both tools and+ longer-running daemons.+ .+ This package adds wrappers around the __effectful__ library, providing an+ implmentation of a ProgramE τ effect wrapping the Program τ monad from+ __core-program__ so that applications needing to use a complicated effect+ system can still access top-level program utilities such as concurrency,+ logging, and telemetry.+category: System+stability: experimental+homepage: https://github.com/aesiniath/unbeliever#readme+bug-reports: https://github.com/aesiniath/unbeliever/issues+author: Juan Raphael Diaz Simões <juanrapha@gmail.com>+maintainer: Andrew Cowie <istathar@gmail.com>+copyright: © 2023- Athae Eredh Siniath and Others+license: MIT+license-file: LICENSE+build-type: Simple+tested-with:+ GHC == 9.2.4++source-repository head+ type: git+ location: https://github.com/aesiniath/unbeliever++library+ exposed-modules:+ Core.Effect.Effectful+ hs-source-dirs:+ lib+ ghc-options: -Wall -Wwarn -fwarn-tabs+ build-depends:+ base >=4.11 && <5+ , core-data+ , core-program >=0.6.2+ , core-text+ , effectful-core+ default-language: Haskell2010
+ lib/Core/Effect/Effectful.hs view
@@ -0,0 +1,221 @@+{-# 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