{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
module Control.Monad.ReaderSoup
(
ReaderSoup_(..)
, IsInSoup_
, IsInSoup
, ArgsForSoupConsumption(..)
, ContextRunner(..)
, Label
, (=:)
, (:::)
, Rec(..)
, consumeSoup
, module Control.Monad.Trans.Reader
, hoist
, MonadReader(..)
, ReaderSoup
, ContextFromName
, SoupContext(..)
, CanBeScoopedIn_
, CanBeScoopedIn
, CanRunSoupContext
, askSoup
, filtering
, picking, scooping, pouring
, ElField(..)
, Spoon
, CookedReaderSoup
, cookReaderSoup
, pickTopping
, eatTopping
, finishBroth
, rioToSpoon, spoonToReaderT
, dipping
, withSpoon
, fromLabel
) where
import Control.Applicative
import Control.Exception.Safe
import Control.Lens (over)
import Control.Monad
import Control.Monad.Base (MonadBase)
import Control.Monad.Fail
import Control.Monad.IO.Unlift
import Control.Monad.Morph (hoist)
import Control.Monad.Reader.Class
import Control.Monad.Trans.Control (MonadBaseControl)
import Control.Monad.Trans.Reader hiding (ask, local, reader)
import Data.Vinyl hiding (record)
import Data.Vinyl.TypeLevel
import GHC.OverloadedLabels
import GHC.TypeLits
newtype ReaderSoup_ (record::((Symbol, *) -> *) -> [(Symbol, *)] -> *) ctxs a = ReaderSoup
{ unReaderSoup ::
ReaderT (record ElField ctxs) IO a }
deriving ( Functor, Applicative, Alternative, Monad, MonadFail, MonadPlus
, MonadIO, MonadUnliftIO, MonadBase IO, MonadBaseControl IO
, MonadCatch, MonadThrow, MonadMask )
type ReaderSoup = ReaderSoup_ ARec
type CookedReaderSoup = ReaderSoup_ Rec
cookReaderSoup :: (NatToInt (RLength ctxs))
=> ReaderSoup ctxs a
-> CookedReaderSoup ctxs a
cookReaderSoup (ReaderSoup (ReaderT act)) =
ReaderSoup $ ReaderT $ act . toARec
pickTopping :: (KnownSymbol l)
=> CookedReaderSoup ( (l:::c) : ctxs ) a
-> ReaderT c (CookedReaderSoup ctxs) a
pickTopping (ReaderSoup (ReaderT actInSoup)) =
ReaderT $ \ctx1 -> ReaderSoup $
ReaderT $ \ctxs -> actInSoup $ Field ctx1 :& ctxs
eatTopping :: (KnownSymbol l)
=> CookedReaderSoup ( (l:::c) : ctxs ) a
-> c
-> CookedReaderSoup ctxs a
eatTopping crs = runReaderT (pickTopping crs)
finishBroth :: CookedReaderSoup '[] a -> IO a
finishBroth (ReaderSoup (ReaderT act)) = act RNil
type family ContextFromName (l::Symbol) :: *
type IsInSoup_ r ctxs l =
( HasField r l ctxs ctxs (ContextFromName l) (ContextFromName l)
, RecElemFCtx r ElField )
type IsInSoup ctxs l = IsInSoup_ ARec ctxs l
askSoup :: (IsInSoup_ r ctxs l)
=> Label l -> ReaderSoup_ r ctxs (ContextFromName l)
askSoup l = ReaderSoup $ rvalf l <$> ask
filtering :: (RecSubset ARec ctxs' ctxs (RImage ctxs' ctxs))
=> ReaderSoup ctxs' a
-> ReaderSoup ctxs a
filtering (ReaderSoup (ReaderT act)) =
ReaderSoup $ ReaderT $ act . rcast
newtype Spoon_ r ctxs (l::Symbol) a = Spoon
{ unSpoon :: ReaderSoup_ r ctxs a }
deriving ( Functor, Applicative, Monad
, MonadIO, MonadUnliftIO, MonadBase IO, MonadBaseControl IO
, MonadCatch, MonadThrow, MonadMask )
type Spoon = Spoon_ ARec
instance (IsInSoup_ r ctxs l, c ~ ContextFromName l)
=> MonadReader c (Spoon_ r ctxs l) where
ask = Spoon $ askSoup $ fromLabel @l
local f (Spoon (ReaderSoup (ReaderT act))) =
Spoon $ ReaderSoup $ ReaderT $
act . over (rlensf (fromLabel @l)) f
dipping :: Label l
-> Spoon_ r ctxs l a
-> ReaderSoup_ r ctxs a
dipping _ = unSpoon
rioToSpoon :: forall l ctxs a r. (IsInSoup_ r ctxs l)
=> ReaderT (ContextFromName l) IO a -> Spoon_ r ctxs l a
rioToSpoon (ReaderT act) = Spoon $ ReaderSoup $ ReaderT $
act . rvalf (fromLabel @l)
spoonToReaderT :: forall l ctxs a r. (IsInSoup_ r ctxs l, KnownSymbol l)
=> Spoon_ r ctxs l a -> ReaderT (ContextFromName l) (ReaderSoup_ r ctxs) a
spoonToReaderT (Spoon (ReaderSoup (ReaderT act))) =
ReaderT $ \v -> ReaderSoup $ ReaderT $ \record ->
act $ rputf (fromLabel @l) v record
class SoupContext c t | c -> t where
toReaderT :: (Monad m) => t m a -> ReaderT c m a
fromReaderT :: (Monad m) => ReaderT c m a -> t m a
type CanBeScoopedIn_ r t ctxs l =
(IsInSoup_ r ctxs l, KnownSymbol l, SoupContext (ContextFromName l) t)
type CanBeScoopedIn t ctxs l = CanBeScoopedIn_ ARec t ctxs l
withSpoon :: forall l ctxs t a r.
(CanBeScoopedIn_ r t ctxs l)
=> t (ReaderSoup_ r ctxs) a
-> Spoon_ r ctxs l a
withSpoon act = Spoon $ ReaderSoup $ ReaderT $ \record ->
runReaderT (unReaderSoup $
(runReaderT (toReaderT act) $
rvalf (fromLabel @l) record))
record
picking :: (CanBeScoopedIn_ r t ctxs l)
=> Label l
-> t IO a
-> ReaderSoup_ r ctxs a
picking lbl = dipping lbl . rioToSpoon . toReaderT
scooping :: (CanBeScoopedIn_ r t ctxs l)
=> Label l
-> t (ReaderSoup_ r ctxs) a
-> ReaderSoup_ r ctxs a
scooping lbl = dipping lbl . withSpoon
pouring :: forall l ctxs t a r.
(CanBeScoopedIn_ r t ctxs l)
=> Label l
-> ReaderSoup_ r ctxs a
-> t (ReaderSoup_ r ctxs) a
pouring _ act = fromReaderT $ spoonToReaderT (Spoon act :: Spoon_ r ctxs l a)
newtype ContextRunner t m = ContextRunner
{ runContext :: forall r. t m r -> m r }
class (NatToInt (RLength (ContextsFromArgs args))) => ArgsForSoupConsumption args where
type ContextsFromArgs args :: [(Symbol, *)]
consumeSoup_ :: Rec ElField args -> CookedReaderSoup (ContextsFromArgs args) a -> IO a
instance ArgsForSoupConsumption '[] where
type ContextsFromArgs '[] = '[]
consumeSoup_ _ = finishBroth
type CanRunSoupContext l t =
(SoupContext (ContextFromName l) t)
instance ( ArgsForSoupConsumption restArgs
, m ~ CookedReaderSoup (ContextsFromArgs restArgs)
, CanRunSoupContext l t )
=> ArgsForSoupConsumption ((l:::ContextRunner t m) : restArgs) where
type ContextsFromArgs ((l:::ContextRunner t m) : restArgs) =
(l:::ContextFromName l) : ContextsFromArgs restArgs
consumeSoup_ (Field args :& restArgs) act =
consumeSoup_ restArgs $
runContext args (fromReaderT (pickTopping act))
consumeSoup :: (ArgsForSoupConsumption args)
=> Rec ElField args -> ReaderSoup (ContextsFromArgs args) a -> IO a
consumeSoup args = consumeSoup_ args . cookReaderSoup