{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}

#if MTL
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
#endif

module Control.Effect.Reader (
	EffectReader, Reader, runReader,
    ask, asks, local
) where

import Control.Monad.Effect

#ifdef MTL
import qualified Control.Monad.Reader.Class as R

instance EffectReader r es => R.MonadReader r (Effect es) where
    ask = ask
    local = local
    reader = asks
#endif

-- | An effect that describes an implicit environment.
newtype Reader r a = Reader (r -> a)
  deriving Functor

type EffectReader r es = (Member (Reader r) es, r ~ ReaderType es)
type family ReaderType es where
    ReaderType (Reader r ': es) = r
    ReaderType (e ': es) = ReaderType es

-- | Retrieves the current environment.
ask :: EffectReader r es => Effect es r
ask = asks id

-- | Retrieves a value that is a function of the current environment.
asks :: EffectReader r es => (r -> a) -> Effect es a
asks = send . Reader

-- | Runs a computation with a modified environment.
local :: EffectReader r es => (r -> r) -> Effect es a -> Effect es a
local f effect = do
    env <- asks f
    run env effect
  where
    run env =
        handle return
        $ intercept (bind env)
        $ defaultRelay

-- | Completely handes a `Reader` effect by providing an
-- environment value to be used throughout the computation.
runReader :: r -> Effect (Reader r ': es) a -> Effect es a
runReader env =
    handle return
    $ eliminate (bind env)
    $ defaultRelay

bind :: r -> Reader r (Effect es b) -> Effect es b
bind env (Reader k) = k env