我们可以尝试以下改变:
- 使用“base”monad 参数化环境记录。
- Make
HasLogger
将环境与“基本”单子关联起来的二参数类型类。
像这样的东西:
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE StandaloneKindSignatures #-}
import Control.Monad.IO.Class
import Control.Monad.Reader
import Data.Kind (Constraint, Type)
type RT m = ReaderT (SomeEnv m) m
type SomeEnv :: (Type -> Type) -> Type
data SomeEnv m = SomeEnv
{ a :: Int,
logger :: String -> RT m (),
-- I'm putting the main fuction in the record,
-- perhaps we'll want to inject it into other logic, later.
myFun :: Int -> RT m Int
}
type HasLogger :: Type -> (Type -> Type) -> Constraint
class HasLogger r m | r -> m where
getLogger :: r -> String -> m ()
instance HasLogger (SomeEnv m) (RT m) where
getLogger = logger
_myFun :: (MonadReader e m, HasLogger e m) => Int -> m Int
_myFun x = do
logger <- reader getLogger
logger "I'm going to multiply a number by itself!"
return $ x * x
Now _myFun
没有MonadIO
约束。
我们可以创建一个示例环境并运行myFun
:
env =
SomeEnv
{ a = 13,
logger = liftIO . putStrLn,
myFun = _myFun
}
doIt :: IO Int
doIt = runReaderT (myFun env 1337) env
该解决方案的一个缺点是环境中的函数签名变得更加复杂,即使使用RT
类型同义词。
Edit:为了简化环境中的签名,我尝试了这些替代定义:
type SomeEnv :: (Type -> Type) -> Type
data SomeEnv m = SomeEnv
{ a :: Int,
logger :: String -> m (), -- no more annoying ReaderT here.
myFun :: Int -> m Int
}
instance HasLogger (SomeEnv m) m where
getLogger = logger
-- Yeah, scary. This newtype seems necessary to avoid an "infinite type" error.
-- Only needs to be defined once. Could we avoid it completely?
type DepT :: ((Type -> Type) -> Type) -> (Type -> Type) -> Type -> Type
newtype DepT env m r = DepT { runDepT :: ReaderT (env (DepT env m)) m r }
deriving (Functor,Applicative,Monad,MonadIO,MonadReader (env (DepT env m)))
instance MonadTrans (DepT env) where
lift = DepT . lift
env' :: SomeEnv (DepT SomeEnv IO) -- only the signature changes here
env' =
SomeEnv
{ a = 13,
logger = liftIO . putStrLn,
myFun = _myFun
}
doIt :: IO Int
doIt = runReaderT (runDepT (myFun env' 1337)) env'
DepT
基本上是一个ReaderT
,但人们知道它的环境是由DeptT
本身。它有通常的情况。
_myFun
不需要改变这个替代定义。