{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE StandaloneKindSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- | Invocation helpers for functions with effects in a monad 'D' and which are
-- stored in the environment 'E' of a reader-like monad 'M'.
module Moo.Prelude (
    self,
    call,
    -- * Re-exports from Moo
    M,
    E,
    D 
) where

import Moo
import Data.Kind
import GHC.TypeLits
import Control.Monad.Reader
import Control.Monad.Dep.Has

type Call :: Type -> Constraint
class Call curried where
    type LiftedD curried :: Type
    -- | Given a way of extracting from the environment 'E' a @curried@
    -- function that ends in a 'D' action, lift the @curried@ 'D'-function into the main
    -- monad 'M'.
    self :: (E -> curried) -> LiftedD curried

instance Call (D r) where
    type LiftedD (D r) = M r
    self :: (E -> D r) -> LiftedD (D r)
self E -> D r
extractor = do
        E
e <- M E
forall r (m :: * -> *). MonadReader r m => m r
ask
        D r -> M r
forall x. D x -> M x
liftD (D r -> M r) -> D r -> M r
forall a b. (a -> b) -> a -> b
$ E -> D r
extractor E
e

instance Call curried' => Call (a -> curried') where
    type LiftedD (a -> curried') = a -> LiftedD curried'
    self :: (E -> a -> curried') -> LiftedD (a -> curried')
self E -> a -> curried'
extractor a
a = 
        let extractor' :: E -> curried'
extractor' = \E
e -> E -> a -> curried'
extractor E
e a
a
        in (E -> curried') -> LiftedD curried'
forall curried. Call curried => (E -> curried) -> LiftedD curried
self @curried' E -> curried'
extractor'

-- | Provided that the environment 'E' 'Control.Monad.Dep.Has' a @component@, and
-- given a way of extracting from the @component@ a @curried@ function that
-- ends in a 'D' action, lift the @curried@ 'D'-function into the main monad
-- 'M'.
--
-- The extractor must be monomorphic on the @component@, so that the intended
-- instance of 'Control.Monad.Dep.Has' is picked. 
--
-- The typical case is for the @component@ to be a parameterized record and for
-- the extractor to be a field accessor.
call :: forall component curried . (Has component D E, Call curried) => (component D -> curried) -> LiftedD curried
call :: (component D -> curried) -> LiftedD curried
call component D -> curried
extractor = (E -> curried) -> LiftedD curried
forall curried. Call curried => (E -> curried) -> LiftedD curried
self (component D -> curried
extractor (component D -> curried) -> (E -> component D) -> E -> curried
forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (d :: * -> *) e. Has component d e => e -> component d
forall (r_ :: (* -> *) -> *) (d :: * -> *) e.
Has r_ d e =>
e -> r_ d
dep @component)