{-# 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 obtaining 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.
--
-- So, this library allows 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 be certain about what effects are involved in each function.
-- * __Effect decoupling:__ You can decouple the implementation of the effects from your application and swap them
--   easily.
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.Effect
import           Cleff.Internal.Interpret
import           Cleff.Internal.Monad
import           Cleff.Internal.TH
import           UnliftIO                 (MonadIO (liftIO), MonadUnliftIO (withRunInIO))

-- $runningEffects
-- To run an effect @T@, we should use an /interpreter/ of @T@, which is a function that has 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 coming together with them.
--
-- By applying interpreters to an 'Eff' computation, you can eventually obtain an /end computation/, where there are no
-- more effects present 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 'Cleff.runPure'; or,
-- * An /impure computation/ with type @'Eff' '['Cleff.IOE'] a@ that can be transformed into an IO computation via
--   'Cleff.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/writing files can be as following:
--
-- @
-- data Filesystem :: 'Effect' where
--   ReadFile :: 'FilePath' -> Filesystem m 'String'
--   WriteFile :: 'FilePath' -> 'String' -> Filesystem m ()
-- @
--
-- 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.
-- For example,
--
-- @
-- 'makeEffect' ''Filesystem
-- @
--
-- generates the following definitions:
--
-- @
-- readFile      :: Filesystem ':>' es => 'FilePath' -> 'Eff' es 'String'
-- readFile  x   =  'send' (ReadFile x)
-- writeFile     :: Filesystem ':>' es => 'FilePath' -> 'String' -> 'Eff' es ()
-- writeFile x y =  'send' (WriteFile x y)
-- @

-- $interpretingEffects
-- An effect can be understood as the "grammar" (or /syntax/) of a small language; however we also need to define the
-- "meaning" (or /semantics/) of the language. In other words, we need to specify the implementation 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.
--
-- This is very easy to do. 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
-- @
--
-- Alternatively, we can also construct an in-memory filesystem in terms of the 'Cleff.State.State' effect via
-- the 'reinterpret' function.
--
-- @
-- runFilesystemPure :: 'Cleff.Fail.Fail' ':>' es => 'Data.Map.Map' 'FilePath' 'String' -> 'Eff' (Filesystem ': es) a -> 'Eff' es a
-- runFilesystemPure fs = 'fmap' 'fst' '.' 'Cleff.State.runState' fs '.' '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)
-- @
--
-- 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 we need to transform computations from
-- arbitrary effect stacks into a specific stack that the effect is currently interpreted into. In other words, they
-- need to thread other effects through themselves. This is why Cleff also 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'.