5

对不起,如果这个问题看起来有点微不足道......它不适合我。我很高兴地编写了以下单子:

type SB i a = ReaderT ( AlgRO i ) (State ( AlgState i ) ) a

这是一个行为良好的单子。ReaderT 是一个 monad 转换器,State 是 State monad,AlgRO 和 AlgState 是在 i 中参数化的数据类型,分别用于可变和只读状态。现在,如果我想用 newtype 制作一个整洁的 monad 转换器,如下所示:

newtype SbT m i a = SbT {
    runSbT:: m ( SB i a )
}

我应该如何进行?我什至无法将(Monad 类型类的)绑定方法放在一起,更不用说(MonadTrans 的)“提升”了......我想自动推导可能会有所帮助,但我想了解它在这种情况下是如何工作的。

提前致谢。

4

2 回答 2

10

我不认为这个定义SbT是你想要的。这定义了functor composition,并假设m参数是 a Functoror Applicative,这应该保留这些属性。但一般来说,像这样的组合不会从其他两个单子中创建一个新的单子。有关该主题的更多信息,请参阅此问题

那么,你如何创建你想要的 monad 转换器呢?虽然 monad 不能直接组合,但可以组合 monad转换器。所以要从现有的变压器中构建一个新的变压器,你基本上只是想给那个组合起一个名字。这与newtype您所拥有的不同,因为您m直接应用,而不是将其传递给变压器堆栈。

关于定义 monad 转换器要记住的一件事是,它们必须以某些方式“向后”工作——当你将复合转换器应用于 monad 时,“最里面”的转换器会首先破解它,然后转换后的 monad 它产生的是下一个变压器输出的东西,&c。请注意,这与将组合函数应用于参数时得到的顺序没有任何不同,例如(f . g . h) x,将参数赋予h第一个,即使f是组合中的“第一个”函数。

好的,所以你的复合转换器需要将它应用到的 m​​onad 传递给最里面的转换器,即,嗯.... 哎呀,原来它SB已经应用于一个 monad。难怪这不起作用。我们需要先删除它。它在哪里?不State——我们可以删除它,但我们不想这样做,因为它是你想要的一部分。嗯,但是等等——State又是什么定义?哦耶:

type State s = StateT s Identity

啊哈,我们走了。让我们把它从那里弄Identity出来。我们从您当前的定义出发:

type SB i a = ReaderT ( AlgRO i ) (State ( AlgState i ) ) a

等价形式:

type SB i a = ReaderT ( AlgRO i ) ( StateT ( AlgState i ) Identity ) a

然后我们踢掉这个懒惰的流浪汉:

type SB' i m a = ReaderT ( AlgRO i ) ( StateT ( AlgState i ) m ) a
type SB i a = SB' i Identity a

但现在SB'看起来可疑地像一个 monad 转换器定义,并且有充分的理由,因为它是。所以我们重新创建了newtype包装器,并抛出了一些实例:

newtype SbT i m a = SbT { getSB :: ReaderT ( AlgRO i ) ( StateT ( AlgState i ) m ) a }

instance (Functor m) => Functor (SbT i m) where
    fmap f (SbT sb) = SbT (fmap f sb)

instance (Monad m) => Monad (SbT i m) where
    return x = SbT (return x)
    SbT m >>= k = SbT (m >>= (getSB . k))

instance MonadTrans (SbT i) where
    lift = SbT . lift . lift

runSbT :: SbT i m a -> AlgRO i -> AlgState i -> m (a, AlgState t)
runSbT (SbT m) e s = runStateT (runReaderT m e) s

有几点需要注意:runSbT这里的函数不是字段访问器,而是我们所知道的堆栈中每个转换器的组合“运行”函数。同样,该lift函数必须为两个内部转换器提升一次,然后添加最终newtype包装器。这两者都使它作为一个单一的单子变压器工作,隐藏了它实际上是一个复合材料的事实。

如果您愿意,通过提升组合转换器的实例来编写实例应该很MonadReader简单MonadState

于 2011-09-14T14:06:07.680 回答
2

你是否打算在你的新类型中添加额外m的东西?我建议如下:

newtype Sb i a = Sb { runSb :: SB i a }

...这应该让你instance Monad (Sb i)更容易写。如果你真的想写一个 monad 转换器,那么你应该一直使用转换器;例如,

type SBT m i a = ReaderT (AlgRO i) (StateT (AlgState i) m) a
newtype SbT m i a = SbT { runSbT :: SBT m i a }

作为第二个兴趣点,η-reducetype同义词通常更可取(因为它们必须始终“完全应用”);这样做,SB看起来SBT像这样:

type SB i = ReaderT (AlgRO i) (State (AlgState i))
type SBT m i = ReaderT (AlgRO i) (StateT (AlgState i) m)
于 2011-09-14T14:06:42.773 回答