我定义了一个简单的monad变换器EntityBuilderT
,它只是ReaderT
上的一个新类型。
data EntityBuilderState = ...
newtype EntityBuilderT m a = EntityBuilderT (ReaderT EntityBuilderState m a)
要将函数包装在新的“环境”中,我编写了以下组合器:
withNewSource :: (Monad m) => String -> EntityBuilderT m a -> EntityBuilderT m a
withNewSource itemId builder = ...
在某些情况下,我还想构建一个更大的变压器堆栈。例如:
f :: MaybeT (EntityBuilderT m) a
显然,由于monad类型不再匹配,我无法将withNewSource
应用于此函数f
。因此,我尝试使用monad-control
来编写这种组合器的新版本。
到目前为止我写的代码如下所示。虽然实例定义似乎没问题,但编译器(GHC 7.4.1)拒绝使用以下消息的代码:
Couldn't match type `IO' with `EntityBuilderT m0'
When using functional dependencies to combine
MonadBaseControl IO IO,
arising from the dependency `m -> b'
in the instance declaration in `Control.Monad.Trans.Control'
MonadBaseControl (EntityBuilderT m0) IO,
arising from a use of `control'
In the expression: control
In the expression: control $ \ run -> withNewSource itemId (run m)
我有点失落。任何人都明白问题到底是什么?
{-# LANGUAGE FlexibleInstances, GeneralizedNewtypeDeriving,
MultiParamTypeClasses, TypeFamilies, UndecidableInstances #-}
import Control.Applicative (Applicative)
import Control.Monad (liftM)
import Control.Monad.Base
import Control.Monad.Trans (MonadTrans)
import Control.Monad.Trans.Control
import Control.Monad.Trans.Maybe (MaybeT)
import Control.Monad.Trans.Reader (ReaderT, withReaderT)
data EntityBuilderState
newtype EntityBuilderT m a = EntityBuilderT { unEB :: ReaderT EntityBuilderState m a }
deriving (Applicative, Functor, Monad, MonadTrans)
instance MonadBase b m => MonadBase b (EntityBuilderT m) where
liftBase = liftBaseDefault
instance MonadTransControl EntityBuilderT where
newtype StT EntityBuilderT a = StEB { unStEB :: StT (ReaderT EntityBuilderState) a }
liftWith f = EntityBuilderT $ liftWith $ \run ->
f $ liftM StEB . run . unEB
restoreT = EntityBuilderT . restoreT . liftM unStEB
instance MonadBaseControl b m => MonadBaseControl b (EntityBuilderT m) where
newtype StM (EntityBuilderT m) a = StMT { unStMT :: ComposeSt EntityBuilderT m a }
liftBaseWith = defaultLiftBaseWith StMT
restoreM = defaultRestoreM unStMT
withNewSource :: (Monad m) => String -> EntityBuilderT m a -> EntityBuilderT m a
withNewSource itemId (EntityBuilderT m) = EntityBuilderT (withReaderT undefined m)
withNewSource' :: String -> MaybeT (EntityBuilderT IO) a -> MaybeT (EntityBuilderT IO) a
withNewSource' itemId m = control $ \run -> withNewSource itemId (run m)
答案 0 :(得分:3)
问题在于,由于基本monad为IO
,run
的类型为MaybeT (EntityBuilderT IO) a -> IO (StM (MaybeT (EntityBuilderT IO) a))
,但您将其返回值用作EntityBuilderT IO
操作。此外,您传递给control
的函数的返回值必须位于IO
,而不是EntityBuilderT IO
。
这是因为您的MonadBaseControl
实例表示您将事物移入已转换的monad m
的基本monad中;由于MaybeT (EntityBuilderT IO)
的基数为IO
,control
从RunInBase (MaybeT (EntityBuilderT IO)) IO
到IO (StM (MaybeT (EntityBuilderT IO)) a)
采用函数。
不幸的是,我没有足够的经验来使用monad-control建议解决方案;也许您可以使用MaybeT
的{{1}}实例来实现“一级关闭”功能?