如何在ReaderT上使用monad-control作为简单的newtype包装器

时间:2012-02-05 14:22:14

标签: haskell monad-transformers

我定义了一个简单的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)

1 个答案:

答案 0 :(得分:3)

问题在于,由于基本monad为IOrun的类型为MaybeT (EntityBuilderT IO) a -> IO (StM (MaybeT (EntityBuilderT IO) a)),但您将其返回值用作EntityBuilderT IO操作。此外,您传递给control的函数的返回值必须位于IO,而不是EntityBuilderT IO

这是因为您的MonadBaseControl实例表示您将事物移入已转换的monad m的基本monad中;由于MaybeT (EntityBuilderT IO)的基数为IOcontrolRunInBase (MaybeT (EntityBuilderT IO)) IOIO (StM (MaybeT (EntityBuilderT IO)) a)采用函数。

不幸的是,我没有足够的经验来使用monad-control建议解决方案;也许您可以使用MaybeT的{​​{1}}实例来实现“一级关闭”功能?