将 monad 组合抽象为变压器

发布于 2024-12-04 18:08:38 字数 486 浏览 3 评论 0原文

抱歉,如果这个问题看起来有点微不足道……它不适合我。 我很高兴地编写了以下 monad:

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

这是一个表现良好的 monad。 ReaderT 是一个 monad 转换器,State 是 State monad,AlgRO 和 AlgState 是 i 中参数化的数据类型,分别表示可变和只读状态。现在,如果我想用 newtype 来制作一个简洁的 monad 转换器,就像这样:

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

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

提前致谢。

Sorry if the question seems a bit trivial... it is not for me.
I have happily composed the following monad:

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

which is, well, a well behaved monad. ReaderT is a monad transformer and State is the State monad, and AlgRO and AlgState are datatypes parametrized in i for mutable and read-only state, respectively. Now, if I want to make of that a neat monad transformer with newtype, something like this:

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

how should I proceed? I can not even manage to put together the bind method (of Monad typeclass), much less "lift" (of MonadTrans)... I guess that automatic derivation could help, but I want to understand how it works in this case.

Thanks in advance.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

凉城已无爱 2024-12-11 18:08:38

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

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

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

好的,所以你的复合变压器需要获取它所应用的 monad 并将其传递给最里面的变压器,也就是,呃......哎呀,原来 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 包装器。这两者都使它作为单个 monad 转换器工作,隐藏了它实际上是一个复合体的事实。

如果您愿意,通过提升组合变压器的实例,为 MonadReaderMonadState 编写实例也应该很简单。

I don't think that definition for SbT is what you want. That defines functor composition, and assuming the m parameter is a Functor or Applicative, this should preserve those properties. But composition like that does not, in general, create a new monad out of two others. See this question for more on that subject.

So, how do you create the monad transformer you want, then? While monads don't compose directly, monad transformers can be composed. So to build a new transformer out of existing ones, you essentially just want to give a name to that composition. This differs from the newtype you have because there you're applying the m directly, instead of passing it in to the transformer stack.

One thing to keep in mind about defining monad transformers is that they necessarily work "backwards" in certain ways--when you apply a composite transformer to a monad, the "innermost" transformer gets the first crack at it, and the transformed monad it produces is what the next transformer out gets to work with, &c. Note that this isn't any different from the order you get when applying a composed function to an argument, e.g. (f . g . h) x gives the argument to h first, even though f is the "first" function in the composition.

Okay, so your composite transformer needs to take the monad it's applied to and pass it to the innermost transformer, which is, uhm.... oops, turns out that SB is already applied to a monad. No wonder this wasn't working. We'll need to remove that, first. Where is it? Not State--we could remove that, but we don't want to, because it's part of what you want. Hmm, but wait--what is State defined as, again? Oh yeah:

type State s = StateT s Identity

Aha, there we go. Let's get that Identity out of there. We go from your current definition:

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

To the equivalent form:

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

Then we kick the lazy bum out:

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

But now SB' looks suspiciously like a monad transformer definition, and with good reason, because it is. So we recreate the newtype wrapper, and toss a few instances out there:

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

A couple things to take note of: The runSbT function here is not the field accessor, but rather a composed "run" function for each transformer in the stack that we know of. Similarly, the lift function has to lift once for the two inner transformers, then add the final newtype wrapper. Both of these make it work as a single monad transformer, hiding the fact that it's actually a composite.

If you'd like, it should be straightforward to write instances for MonadReader and MonadState as well, by lifting the instances for the composed transformers.

雨落□心尘 2024-12-11 18:08:38

您是否打算在新类型中添加一个额外的 m 内容?我建议如下:

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

...这应该使您的实例 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 }

作为第二个兴趣点,它通常比 η-reduce type 同义词更可取(因为它们必须始终“完全应用”);使用 SBSBT 执行此操作将如下所示:

type SB i = ReaderT (AlgRO i) (State (AlgState i))
type SBT m i = ReaderT (AlgRO i) (StateT (AlgState i) m)

Did you intend to wrap an additional m around things in your newtype? I would suggest the following:

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

...which should make your instance Monad (Sb i) a bit easier to write. If you're really trying to write a monad transformer, then you should use transformers all the way down; for example,

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

As a second point of interest, it's often preferable to η-reduce type synonyms (since they must always be "fully applied"); doing this with SB and SBT would look like this:

type SB i = ReaderT (AlgRO i) (State (AlgState i))
type SBT m i = ReaderT (AlgRO i) (StateT (AlgState i) m)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文