content: {-# LANGUAGE Trustworthy #-}
  -- |
  -- Copyright: (c) 2021 Xy Ren
  -- License: BSD3
  -- Maintainer: xy.r@outlook.com
  -- Stability: experimental
  -- Portability: non-portable (GHC only)
  --
  -- This library implements an /extensible effects system/, where sets of monadic actions ("effects") are encoded as
  -- datatypes, tracked at the type level and can have multiple different implementations. This means you can swap out
  -- implementations of certain monadic actions in mock tests or in different environments. The notion of "effect" is
  -- general here: it can be an 'IO'-performing side effect, or just reading the value of a static global environment.
  --
  -- In particular, this library consists of
  --
  -- * The 'Eff' monad, which is the core of an extensible effects system. All effects are performed within it and it
  --   will be the "main" monad of your application. This monad tracks effects at the type level.
  -- * A set of predefined general effects, like 'Cleff.Reader.Reader' and 'Cleff.State.State' that can be used out of
  --   the box.
  -- * Combinators for defining new effects and interpreting them /on your own/. These effects can be translated in terms
  --   of other already existing effects, or into operations in the 'IO' monad.
  --
  -- In terms of structuring your application, this library helps you to do two things:
  --
  -- * __Effect management:__ The 'Eff' monad tracks what effects are used explicitly at the type level, therefore you
  --   are able to enforce what effects are involved in each function, and avoid accidentally introduced behaviors.
  -- * __Effect decoupling:__ You can swap between the implementations of the effects in your application easily,
  --   so you can refactor and test your applications with less clutter.
  module Cleff
    ( -- * Using effects
      Eff
    , (:>)
    , (:>>)
    , Effect
    , IOE
      -- ** Running effects
      -- $runningEffects
    , runPure
    , runIOE
      -- * Defining effects
      -- $definingEffects
    , send
    , sendVia
    , makeEffect
    , makeEffect_
      -- * Trivial effects handling
    , raise
    , raiseN
    , inject
    , subsume
    , subsumeN
    , KnownList
    , Subset
      -- * Interpreting effects
      -- $interpretingEffects
    , Handler
    , interpret
    , reinterpret
    , reinterpret2
    , reinterpret3
    , reinterpretN
    , interpose
    , impose
    , imposeN
      -- ** Interpreting in terms of 'IO'
    , HandlerIO
    , interpretIO
      -- ** Translating effects
    , Translator
    , transform
    , translate
      -- ** Transforming interpreters
    , raiseUnder
    , raiseNUnder
    , raiseUnderN
    , raiseNUnderN
      -- * Combinators for interpreting higher order effects
      -- $higherOrderEffects
    , Handling
    , toEff
    , toEffWith
    , withFromEff
      -- ** Interpreting 'IO'-related higher order effects
    , withToIO
    , fromIO
      -- * Miscellaneous
    , type (~>)
    , type (++)
    , MonadIO (..)
    , MonadUnliftIO (..)
    ) where
  
  import           Cleff.Internal.Base
  import           Cleff.Internal.Interpret
  import           Cleff.Internal.Monad
  import           Cleff.Internal.TH
  
  -- $runningEffects
  -- To run an effect @T@, we should use an /interpreter/ of @T@, which is a function that has a type like this:
  --
  -- @
  -- runT :: 'Eff' (T : es) a -> 'Eff' es a
  -- @
  --
  -- Such an interpreter provides an implementation of @T@ and eliminates @T@ from the effect stack. All builtin effects
  -- in @cleff@ have interpreters out of the box in their respective modules.
  --
  -- By applying interpreters to an 'Eff' computation, you can eventually obtain an /end computation/, where there are no
  -- more effects to be interpreted on the effect stack. There are two kinds of end computations:
  --
  -- * A /pure computation/ with the type @'Eff' '[] a@, which you can obtain the value via 'runPure'; or,
  -- * An /impure computation/ with type @'Eff' '['IOE'] a@ that can be unwrapped into an IO computation via
  --   'runIOE'.
  
  -- $definingEffects
  -- An effect should be defined as a GADT and have the kind 'Effect'. Each operation in the effect is a constructor of
  -- the effect type. For example, an effect supporting reading and writing files can be like this:
  --
  -- @
  -- data Filesystem :: 'Effect' where
  --   ReadFile :: 'FilePath' -> Filesystem m 'String'
  --   WriteFile :: 'FilePath' -> 'String' -> Filesystem m ()
  -- @
  --
  -- Here, @ReadFile@ is an operation that takes a 'FilePath' and returns a 'String', presumably the content of the file;
  -- @WriteFile@ is an operation that takes a 'FilePath' and a 'String' and returns @()@, meaning it only performs
  -- side effects - presumably writing the 'String' to the file specified.
  --
  -- Operations constructed with these constructors can be performed via the 'send' function. You can also use the
  -- Template Haskell function 'makeEffect' to automatically generate definitions of functions that perform the effects.
  
  -- $interpretingEffects
  -- An effect can be understood as the /syntax/ of a tiny language; however we also need to define the /meaning/ (or
  -- /semantics/) of the language. In other words, we need to specify the implementations of effects.
  --
  -- In an extensible effects system, this is achieved by writing /effect handlers/, which are functions that transforms
  -- operations of one effect into other "more primitive" effects. These handlers can then be used to make interpreters
  -- with library functions that we'll now see.
  --
  -- For example, for the @Filesystem@ effect:
  --
  -- @
  -- data Filesystem :: 'Effect' where
  --   ReadFile :: 'FilePath' -> Filesystem m 'String'
  --   WriteFile :: 'FilePath' -> 'String' -> Filesystem m ()
  -- @
  --
  -- We can easily handle it in terms of 'IO' operations via 'interpretIO', by pattern matching on the effect
  -- constructors:
  --
  -- @
  -- runFilesystemIO :: 'IOE' ':>' es => 'Eff' (Filesystem : es) a -> 'Eff' es a
  -- runFilesystemIO = 'interpretIO' \\case
  --   ReadFile path           -> 'readFile' path
  --   WriteFile path contents -> 'writeFile' path contents
  -- @
  --
  -- Specifically, a @ReadFile@ operation is mapped to a real 'readFile' IO computation, and similarly a @WriteFile@
  -- operation is mapped to a 'writeFile' computation.
  --
  -- An effect is a set of abstract operations, and naturally, they can have more than one interpretations. Therefore,
  -- here we can also construct an in-memory filesystem that reads from and writes into a 'Cleff.State.State' effect, via
  -- the 'reinterpret' function that adds another effect to the stack for the effect handler to use:
  --
  -- @
  -- filesystemToState
  --   :: 'Cleff.Fail.Fail' ':>' es
  --   => 'Eff' (Filesystem : es) a
  --   -> 'Eff' ('Cleff.State.State' ('Data.Map.Map' 'FilePath' 'String') : es) a
  -- filesystemToState = 'reinterpret' \\case
  --   ReadFile path -> 'Cleff.State.gets' ('Data.Map.lookup' path) >>= \\case
  --     'Nothing'       -> 'fail' ("File not found: " ++ 'show' path)
  --     'Just' contents -> 'pure' contents
  --   WriteFile path contents -> 'Cleff.State.modify' ('Data.Map.insert' path contents)
  -- @
  --
  -- Here, we used the 'reinterpret' function to introduce a @'Cleff.State.State' ('Data.Map.Map' 'FilePath' 'String')@ as
  -- the in-memory filesystem, making @filesystemToState@ a /reinterpreter/ that "maps" an effect into another effect.
  -- We also added a @'Cleff.Fail.Fail' ':>' es@ constraint to our reinterpreter so that we're able to report errors.
  -- To make an /interpreter/ out of this is simple, as we just need to interpret the remaining 'Cleff.State.State'
  -- effect:
  --
  -- @
  -- runFilesystemPure
  --   :: 'Cleff.Fail.Fail' ':>' es
  --   => 'Data.Map.Map' 'FilePath' 'String'
  --   -> 'Eff' (Filesystem : es) a
  --   -> 'Eff' es a
  -- runFilesystemPure fs
  --   = 'fmap' 'fst'           -- runState returns (Eff es (a, s)), so we need to extract the first component to get (Eff es a)
  --   . 'Cleff.State.runState' fs        -- (State (Map FilePath String) : es) ==> es
  --   . filesystemToState  -- (Filesystem : es) ==> (State (Map FilePath String) : es)
  -- @
  --
  -- Both of these interpreters can then be applied to computations with the @Filesystem@ effect to give different
  -- implementations to the effect.
  
  -- $higherOrderEffects
  -- /Higher order effects/ are effects whose operations take other effect computations as arguments. For example, the
  -- 'Cleff.Error.Error' effect is a higher order effect, because its 'Cleff.Error.CatchError' operation takes an effect
  -- computation that may throw errors and also an error handler that returns an effect computation:
  --
  -- @
  -- data Error e :: 'Effect' where
  --   ThrowError :: e -> Error e m a
  --   CatchError :: m a -> (e -> m a) -> Error e m a
  -- @
  --
  -- More literally, an high order effect makes use of the monad type paramenter @m@, while a first order effect, like
  -- 'Cleff.State.State', does not.
  --
  -- It is harder to write interpreters for higher order effects, because the operations of these effects carry
  -- computations from arbitrary effect stacks, and we'll need to convert the to the current effect stack that the effect
  -- is being interpreted into. Fortunately, Cleff provides convenient combinators for doing so.
  --
  -- In a 'Handler', you can temporarily "unlift" a computation from an arbitrary effect stack into the current stack via
  -- 'toEff', explicitly change the current effect interpretation in the computation via 'toEffWith', or directly express
  -- the effect in terms of 'IO' via 'withToIO'.
  