如何设计一个单体堆栈? [英] How to design a monadic stack?

查看:139
本文介绍了如何设计一个单体堆栈?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

您如何设计和构建您的单体堆栈?我第一次需要构建一个单一堆栈(使用变压器)来解决一个现实世界的问题,但是我并不完全确定哪个顺序来堆栈变压器。正如你已经知道的,只要一个计算有一个 * - > * ,基本上任何东西都可以在变压器中扮演内部monad的角色,因此有两个问题:





  • 应该驱动设计?直觉?类型? (例如,根据您的API的需求来形成堆栈)

  • 每个堆栈都是同构的(在一定程度上),或者如果我错误地构建堆栈,我可能会结束直到无法使用某些潜在的单子或者有一个大涨的混乱的电梯。电梯 。 [...] 我的直觉会表明,如果变压器得到某些实例(例如MonadReader,MonadIO等,像 mtl do)中的大多数变压器一样,则不需要按顺序我把变压器。



我有兴趣从经验丰富的Haskellers听取关于最佳做法或经验法则的听证。 b
$ b

forever $ print谢谢!



A。

解决方案

需要经验。有一件事要记住的是,monad变压器不知道有关monad的变化,所以外面的一个是被内在的行为束缚。所以

  StateT(ListT m)a 

首先是由于内部monad而导致的非确定性计算。然后,将非确定性作为正常的,你添加状态 - 即非确定性的每个分支都将有自己的状态。



ListT (StateT sm)a ,它主要是有状态的,即整个计算只有一个状态(模数 m ),而计算将在状态下采取单线程,因为这是 State 的意思。非确定性将是最重要的 - 所以分支机构将能够观察以前失败的分支机构的状态变化。 (在这种特殊的组合中,这真的很奇怪,我从来不需要它)。



这是一个



我也觉得扩展到实现类型有帮助,给我一个感觉,它是什么样的计算。 ListT 很难扩展,但您可以将其视为不确定,而 StateT 很容易扩展。所以对于上面的例子,我会看看

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

它需要进入状态,并返回许多传出状态。这给了你一个如何工作的想法。类似的方法是查看您的堆栈所需的运行函数的类型 - 这与您需要的信息和所需的信息相匹配吗? p>

以下是一些经验法则。他们不能代替花时间弄清楚你通过扩展和查找真正需要哪一个,但如果你只是寻找添加功能在一种必要的意义上,那么这可能是有帮助的。



ReaderT WriterT StateT 是最常见的变压器。首先,它们都相互通勤,所以你把它们放在什么样的顺序是无关紧要的(尽管如此,考虑使用 RWS )。另外,在实践中,我通常希望这些在外面,更丰富的变压器像 ListT LogicT ContT



ErrorT MaybeT 通常在上面三个外面;我们来看看 MaybeT StateT 的交互方式:

  MaybeT(StateT sm)a =〜StateT sm(Maybe a)=〜s  - > m(可能a,s)
StateT(MaybeT m)a =〜s - >可能m(a,s)=〜s - > m(可能(a,s))

MaybeT 在外部,即使计算失败,状态变化是可观察的。当$ code> MaybeT 在内部,如果计算失败,您不会得到一个状态,所以您必须中止在失败的计算中发生的任何状态更改。你想要哪一个取决于你想要做什么 - 但是,前者与命令式程序员的直觉相对应。 (不一定是要努力的东西)



我希望这能让您了解如何考虑变压器堆栈,因此您有更多工具来分析您的堆栈应该看起来像。如果您将问题确定为单体计算,则获得单体对象是最重要的决策之一,并不总是容易的。花时间探索可能性。


How do you design and build your monadic stacks? For the first time I need to build a monadic stack (using transformers) to solve a real world problem, but I'm not thoroughly sure in which order to stack the transformers. As you already know, as long as a computation has kind * -> *, basically anything can play the role of the inner monad in a transformer, thus a couple of questions:

  • Should some particular transformer be at the top of the stack (e.g. ReaderT? WriterT?)
  • What should drive the design? Intuition? Types? (e.g. shape the stack according to your API's needs)
  • Is every stack isomorphic to each other (to a certain extent) or is it likely that, if I build my stack incorrectly I might end up to not being able to use certain underlying monads or to have a big bloated mess of lift . lift . liftIO [...]? My gut feeling would suggest that, if the transformers derive some instances (e.g. MonadReader, MonadIO, etc, like most transformers in mtl do), it shouldn't matter in which order I put the transformers.

I'm interest in hearing from seasoned Haskellers about best practices or rules of thumb.

forever $ print "Thanks!"

A.

解决方案

It takes experience. One thing to remember is that the monad transformer does not know anything about the monad it is transforming, so the outer one is "bound" by the inner one's behavior. So

StateT s (ListT m) a

is, first and foremost, a nondeterministic computation because of the inner monad. Then, taking nondeterminism as normal, you add state -- i.e. each "branch" of the nondeterminism will have its own state.

Constrast with ListT (StateT s m) a, which is primarily stateful -- i.e. there will only be one state for the whole computation (modulo m), and the computation will act "single threaded" in the state, because that's what State means. The nondeterminism will be on top of that -- so branches will be able to observe state changes of previous failed branches. (In this particular combination, that's really weird, and I've never needed it).

Here is a diagram by Dan Piponi which gives some helpful intuition:

I also find it helpful to expand to the implementation type, to give me a feel for what kind of computation it is. ListT is hard to expand, but you can see it as "nondeterminsm", and StateT is easy to expand. So for the above example, I'd look at

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

I.e. it takes an incoming state, and returns many outgoing states. This gives you an idea of how it's going to work. A similar approach is to look at the type of the run function that you would need for your stack -- does this match the information you have and the information you need?

Here are some rules of thumb. They are no substitute for taking the time to figure out which one you really need by expanding and by looking, but if you are just looking for "adding features" in a sort of imperative sense, then this might be helpful.

ReaderT, WriterT, and StateT are the most common transformers. First, they all commute with each other, so it is irrelevant what order you put them in (Consider using RWS if you are using all three, though). Also, in practice, I usually want these on the outside, with "richer" transformers like ListT, LogicT, and ContT on the inside.

ErrorT and MaybeT usually go on the outside of the above three; let's look at how MaybeT interacts with 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))

When MaybeT is on the outside, a state change is observable even if the computation fails. When MaybeT is on the inside, if the computation fails, you don't get a state out, so you have to abort any state changes that happened in the failing computation. Which one of these you want depends on what you are trying to do -- the former, however, corresponds to imperative programmers' intuitions. (Not that that's necessarily something to be strived for)

I hope this gave you an idea of how to think about transformer stacks, so you have more tools to analyze what your stack should look like. If you identify a problem as a monadic computation, getting the monad right is one of the most important decisions to make, and it's not always easy. Take your time and explore the possibilities.

这篇关于如何设计一个单体堆栈?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆