1

我已经定义了一个简单的 monad 转换器,EntityBuilderT它只是一个 newtype over 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

显然,我不能应用withNewSource这个函数f,因为 monad 类型不再匹配。因此,我尝试使用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)
4

1 回答 1

3

问题是,因为基础 monad 是IO,run有 type MaybeT (EntityBuilderT IO) a -> IO (StM (MaybeT (EntityBuilderT IO) a)),但是你使用它的返回值作为一个EntityBuilderT IO动作。此外,您传递给的函数的返回值control必须是 in IO,而不是EntityBuilderT IO

这是因为您的MonadBaseControl实例说您将事物提升到转换后的 monad 的基本 monad 中m;因为MaybeT (EntityBuilderT IO)is的基数IOcontrol所以函数 fromRunInBase (MaybeT (EntityBuilderT IO)) IOIO (StM (MaybeT (EntityBuilderT IO)) a)

不幸的是,我对 monad-control 没有足够的经验来提出解决方案。也许您可以使用MaybeT'MonadTransControl实例来实现“降级”功能?

于 2012-02-05T14:30:01.370 回答