28

你如何设计和构建你的一元堆栈?我第一次需要构建一个单子堆栈(使用变压器)来解决现实世界的问题,但我不完全确定以什么顺序堆叠变压器。如您所知,只要计算有 kind * -> *,基本上任何东西都可以在转换器中扮演内部 monad 的角色,因此有几个问题:

  • 某些特定的转换器是否应该位于堆栈的顶部(例如 ReaderT?WriterT?)
  • 什么应该驱动设计?直觉?类型?(例如,根据您的 API 需求调整堆栈)
  • 每个堆栈是否彼此同构(在一定程度上),或者如果我错误地构建堆栈,我可能最终无法使用某些底层 monad 或有一大堆臃肿的lift . lift . liftIO [...]. 我的直觉表明,如果转换器派生一些实例(例如 MonadReader、MonadIO 等,就像大多数转换器mtl所做的那样),那么我放置转换器的顺序并不重要。

我有兴趣从经验丰富的 Haskeller 那里听到有关最佳实践或经验法则的信息。

forever $ print "Thanks!"

一个。

4

2 回答 2

28

这需要经验。要记住的一件事是,monad 转换器对它正在转换的 monad 一无所知,因此外部的 monad 受到内部行为的“约束”。所以

StateT s (ListT m) a

由于内部单子,首先是不确定的计算。然后,以非确定性为常态,添加状态——即非确定性的每个“分支”都有自己的状态。

与 相比ListT (StateT s m) a,它主要是有状态的——即整个计算只有一个状态(模m),并且计算将在状态中“单线程”运行,因为这就是State意思。非确定性将是最重要的——因此分支将能够观察到先前失败分支的状态变化。(在这个特殊的组合中,这真的很奇怪,而且我从来不需要它)。

这是Dan Piponi的图表,它提供了一些有用的直觉:

单子涂鸦

我还发现扩展实现类型很有帮助,让我了解它是什么类型的计算。 ListT很难扩展,但您可以将其视为“不确定性”,并且StateT很容易扩展。所以对于上面的例子,我会看

StateT s (ListT m) a =~ s -> ListT m (a,s)

即它接受一个传入状态,并返回许多传出状态。这让您了解它是如何工作的。一种类似的方法是查看run您的堆栈所需的函数类型——这是否与您拥有的信息和您需要的信息相匹配?

这里有一些经验法则。它们无法替代花时间通过扩展和查看来确定您真正需要的功能,但如果您只是在某种命令意义上寻找“添加功能”,那么这可能会有所帮助。

ReaderT, WriterT, 和StateT是最常见的变压器。首先,它们都相互通勤,所以你把它们放在什么顺序是无关紧要的(RWS如果你同时使用这三个,请考虑使用)。此外,在实践中,我通常希望这些在外部,在内部带有“更丰富”的变形金刚,如ListT,LogicTContT

ErrorT并且MaybeT通常在上述三者之外;让我们看看如何MaybeT与 交互StateT

MaybeT (StateT s m) a =~ StateT s m (Maybe a) =~ s -> m (Maybe a, s)
StateT s (MaybeT m) a =~ s -> MaybeT m (a,s) =~ s -> m (Maybe (a,s))

MaybeT在外部时,即使计算失败,也可以观察到状态变化。当MaybeT在内部时,如果计算失败,您不会得到状态,因此您必须中止在失败计算中发生的任何状态更改。你想要哪一个取决于你想要做什么——然而,前者对应于命令式程序员的直觉。(并不是说这一定是要努力的事情)

我希望这能让你了解如何考虑变压器堆栈,这样你就有更多的工具来分析你的堆栈应该是什么样子。如果您将问题识别为 monadic 计算,那么让 monad 正确是最重要的决定之一,而且并不总是那么容易。花点时间探索各种可能性。

于 2013-05-10T15:06:27.313 回答
12

这是一个相当广泛的问题。我只是给你一些基本的想法。

首先,我建议尽可能保持基本单子多态。这将允许您在纯设置和 IO 设置中重用代码。这也将使您的代码更具可组合性。使用各种类似的类MonadIO也可以帮助你的代码保持更多的多态性,这通常是一件好事。

需要注意的一件重要事情是,您的 monad 转换器的顺序实际上控制了它们的语义。我最喜欢的例子是将ListT¹ 之类的东西与EitherT错误处理相结合。如果你ListT在外面有,整个计算可能会失败并出现错误。如果你EitherT在外面,那么每个分支都可以单独失败。因此,您实际上可以通过更改转换器的顺序来控制错误与非确定性相互作用的方式!

如果您使用的 monad 转换器不依赖于顺序 - 例如,组合ReaderTand并不重要WriterT,我相信 - 然后只需按耳朵播放,并选择最适合您的应用程序的任何东西。这是一种随着经验而变得更容易的选择。

¹:ListTfromControl.Monad.Trans有一些问题,所以假设它ListT做对了

于 2013-05-09T08:48:58.870 回答