有人在野外遇到过 Monad Transformer 吗?
在我的业务领域 - 金融机构的后台 IT - 软件组件携带全局配置、记录其进度、进行某种错误处理/计算短路是很常见的......可以通过 Haskell 中的 Reader-、Writer-、 Maybe-monad 等很好地建模,并与 monad 转换器组合在一起。
但似乎存在一些缺点: monad 转换器背后的概念非常棘手且难以理解,monad 转换器导致非常复杂的类型签名,并且它们会造成一些性能损失。
所以我想知道: monad 转换器在处理上述常见任务时是最佳实践吗?
In my area of business - back office IT for a financial institution - it is very common for a software component to carry a global configuration around, to log its progress, to have some kind of error handling / computation short circuit... Things that can be modelled nicely by Reader-, Writer-, Maybe-monads and the like in Haskell and composed together with monad transformers.
But there seem to some drawbacks: The concept behind monad transformers is quite tricky and hard to understand, monad transformers lead to very complex type signatures, and they inflict some performance penalty.
So I'm wondering: Are monad transformers best practice when dealing with those common tasks mentioned above?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
Haskell 社区在这个问题上存在分歧。
John Hughes 报告说,他发现教授 monad 转换器比教授 monad 更容易,而且他的学生采用“转换器优先”的方法做得更好。
GHC 开发人员通常避免使用 monad 转换器,而更喜欢汇总自己的 monad,集成他们需要的所有功能。 (就在今天,我被明确告知,GHC 将不会使用我三天前定义的 monad 转换器。)
对我来说,monad 转换器很像无点编程(即,没有点的编程)命名变量),这是有道理的;毕竟,它们在类型级别上完全是无点编程。我从来不喜欢无点编程,因为能够偶尔引入名称很有用。
我在实践中观察到的是
Hackage 上可用的 monad 转换器的数量非常多,而且大多数都非常简单。这是该问题的一个典型实例,学习大型库比构建自己的实例更困难。
像 Writer、State 和 Environment 这样的 Monad 非常简单,我看不出 monad 转换器有什么好处。
像 Writer、State 和 Environment 这样的 Monad
Monad 转换器的亮点在于模块化和重用。 Liang、Hudak 和 Jones 在他们的里程碑式论文 "Monad 中精美地演示了这一特性变压器和模块化解释器”。
我想说不。单子转换器的最佳实践是您拥有相关抽象的产品线,您可以通过组合和重用来创建这些抽象monad 转换器以不同的方式。在这种情况下,您可能会开发许多对您的问题域很重要的 monad 转换器(例如被 GHC 拒绝的那个),并且您 (a) 以多种方式组合它们; (b) 实现大多数变压器的大量再利用; (c) 在每个 monad 转换器中封装一些重要的东西。
我的 monad 转换器因 GHC 被拒绝,不符合上述任何标准 (a)/(b)/(c)。
The Haskell community is split on this issue.
John Hughes reports that he finds it easier to teach monad transformers than to teach monads, and that his students do better with a "transformers first" approach.
The GHC developers generally avoid monad transformers, preferring to roll up their own monads which integrate all the features they need. (I was told just today in no uncertain terms that GHC will not use a monad transformer I defined three days ago.)
To me, monad transformers are a lot like point-free programming (i.e., programming without named variables), which makes sense; after all, they are exactly programming point-free at the type level. I've never like point-free programming because it's useful to be able to introduce the occasional name.
What I observe in practice is
The number of monad transformers available on Hackage is very great, and most of them are pretty simple. This is a classic instance of the problem where it's harder to learn a large library than to roll your own instances.
Monads like Writer, State, and Environment are so simple that I don't see much benefit to monad transformers.
Where monad transformers shine is in modularity and reuse. This property is beautifully demonstrated by Liang, Hudak, and Jones in their landmark paper "Monad Transformers and Modular Interpreters".
I would say not. Where monad transformers are best practice is where you have a product line of related abstractions which you can create by composing and reusing monad transformers in different ways. In a case like this you probably develop a number of monad transformers that are important for your problem domain (like the one that was rejected for GHC) and you (a) compose them in multiple ways; (b) achieve a significant amount of reuse for most transformers; (c) are encapsulating something nontrivial in each monad transformer.
My monad transformer which was rejected for GHC did not meet any of the criteria (a)/(b)/(c) above.
我认为这有点夸张:
Monad 转换器不是您唯一的选择,您可以编写自定义 Monad,使用延续 Monad。您在 IO(全局)、ST(本地和受控,无 IO 操作)、MVar(同步)、TVar(事务)中具有可变引用/数组。
我听说 Monad 变压器的潜在效率问题可以通过在 mtl/transformers 库的源代码中添加 INLINE pragma 来绑定/返回来缓解。
I Think this is a bit of an exaggeration:
Monad transformers are not your only options, you could write a custom Monad, use a continuation Monad. You have mutable references/arrays in IO (global), ST (local and controlled, no IO actions), MVar (synchronizing), TVar (transactional).
I've heard that the potential efficiency issues with Monad transformers could be mitigated just by adding INLINE pragmas to bind/return in the source of mtl/transformers library.
当我学习 monad 时,我使用 StateT ContT IO 堆栈构建了一个应用程序来创建离散事件模拟库;延续用于存储单子线程,StateT 保存可运行线程队列和其他用于等待各种事件的挂起线程的队列。效果很好。我不知道如何为 newtype 包装器编写 Monad 实例,所以我只是将其设为类型同义词,效果非常好。
这些天我可能会从头开始创建自己的 monad。然而,每当我这样做时,我都会发现自己正在查看“All About Monads”和 MTL 的来源,以提醒我绑定操作是什么样子,所以从某种意义上说,我仍然在考虑 MTL 堆栈,即使结果是一个自定义单子。
Back when I was learning monads I built an application using a stack of StateT ContT IO to create a discrete event simulation library; the continuations were used to store monadic threads, with the StateT holding the runnable thread queue and other queues used for suspended threads waiting for various events. It worked quite well. I couldn't figure out how to write the Monad instance for a newtype wrapper, so I just made it a type synonym and that worked pretty well.
These days I would probably have rolled my own monad from scratch. However whenever I do this I find myself looking at "All About Monads" and the source of the MTL to remind me what the bind operations look like, so in a sense I'm still thinking in terms of an MTL stack even though the result is a custom monad.
我最近“迷上了”F# 上下文中的 monad 组合。我写了一个强烈依赖状态 monad 的 DSL:所有组件都依赖状态 monad:解析器(基于状态 monad 的解析器 monad)、变量匹配表(内部类型不止一个)、标识符查找表。由于这些组件一起工作,它们依赖于相同的状态单子。因此,存在将不同本地状态组合在一起的状态组合概念,以及赋予每个算法自己的状态可见性的状态访问器概念。
最初,该设计实际上是“只是一个大状态单子”。但后来我开始需要只有本地生命周期的状态,但仍然处于“持久”状态的背景下(同样,所有这些状态都由状态单子管理)。为此,我确实需要引入状态单子转换器来增强状态并将状态单子一起调整。我还添加了一个变压器,可以在状态 monad 和延续状态 monad 之间自由移动,但我懒得使用它。
因此,回答这个问题:是的,monad 转换器存在于“野外”。但我强烈反对“开箱即用”使用它们。使用简单的构建块编写应用程序,在模块之间使用手工制作的小型桥梁,如果您最终使用了诸如 monad 转换器之类的东西,那就太好了;不要从那里开始。
关于类型签名:我开始认为这种类型的编程与下蒙眼国际象棋非常相似(而且我不是国际象棋棋手):您的技能水平需要达到您“看到”您的功能的程度和类型组合在一起。类型签名大多最终会分散注意力,除非您出于安全原因明确想要添加类型约束(或者因为编译器强制您提供它们,例如使用 F# 记录)。
I recently "fell" on monad composition in the context of F#. I wrote a DSL with a strong reliance on the state monad: All components rely on the state monad: the parser (parser monad based on state monad), variable matching tables (more than one for internal types), identifier look up tables. And as these components all work together, they rely on the same state monad. Therefore there is a notion of state composition that brings together the different local states, and the notion of state accessors that give each algo their own state visibility.
Initially, the design was really "just one big state monad". But then I started needing states with only local life times, and yet still in the context of the "persistent" state (and again, all these states are managed by state monads). For that I did need to introduce state monad transformers that augment the state and adapt the state monads together. I also added a transformer to move freely between a state monad and a continuation state monad but I have not bothered to use it.
Therefore, to answer the question: yes, monad transformers exist in the "wild". Yet I would argue strongly against using them "out of the box". Write your application with simple building blocks, using small hand crafted bridges between your modules, if you do end up using something like a monad transformer, that's great; Do not start from there.
And about the type signatures: I have come to think of this type of programming as something very similar to playing blindfold chess (and I am not a chess player): your skill level needs to be at the point that you "see" your functions and types fitting together. The type signatures mostly end up being a distraction, unless you explicitly want to add type constraints for safety reasons (or because the compiler forces you to give them such as with F# records).
在我看来,关于“纯粹”/“非纯粹”一词似乎有不止一种概念。您的定义“IO = 不纯,其他一切 = 纯”听起来类似于 Peyton-Jones 在“驯服效果”中所说的内容(http://ulf.wiger.net/weblog/2008/02/29/peyton-jones-taming-effects-下一个大挑战/)。另一方面,现实世界 Haskell(在 Monad Transformer 章节的最后几页)将纯函数与一般的 Monadic 函数进行了对比 - 认为这两个世界都需要不同的库。顺便说一句,有人可能会说 IO 也是纯粹的,它的副作用被封装在类型为 RealWorld -> 的 State 函数中。 (a,现实世界)。毕竟,Haskell 称自己是一种纯粹的函数式语言(我认为包括 IO :-)。)
我的问题不是理论上可以做什么,而是从软件工程的角度来看什么已被证明是有用的。 Monad 转换器允许效果的模块化(以及一般的抽象),但这就是编程应该走向的方向吗?
There seem to me more than one notion about the term pure / non-pure. Your definition "IO = unpure, everything else = pure" sounds similar to what Peyton-Jones talks about in "Taming effects" (http://ulf.wiger.net/weblog/2008/02/29/peyton-jones-taming-effects-the-next-big-challenge/). On the other hand, the Real World Haskell (in the final pages of the Monad Transformer chapter) contrasts pure functions to monadic function in general - arguing that you need different libraries for both worlds. BTW, one could argue that IO is pure as well, it's side effects being encapsulated in a State function with type RealWorld -> (a, RealWorld). After all, Haskell calls itself a purely functional language (IO included, I presume :-).)
My question is not so much about what can be done theoretically, but more about what has been proven useful from a Software Engineering point of view. Monad transformers allow for modularity of effects (and abstractions in general), but is that the direction programming should be heading to?
我认为这是一个误解,只是IO monad不纯粹。像 Write/T/Reader/T/State/T/ST 这样的 monad 仍然是纯功能性的。您可以编写一个纯函数,在内部使用这些 monad 中的任何一个,就像这个完全无用的示例:
所有这一切都是隐式地线程化/探测状态,您自己手动显式执行的操作,这里的 do-notation 只是给您一些不错的东西语法糖,使其看起来势在必行。你不能在这里做任何IO操作,你不能调用任何外部函数。 ST monad 允许您在本地范围内拥有真正的可变引用,同时拥有纯函数接口,并且您不能在其中执行任何 IO 操作,它仍然是纯函数式的。
你无法避免一些信息作战行动,但你不想在所有事情上都依赖信息作战,因为那是任何东西都可以去的地方,导弹可以发射,你没有控制权。 Haskell 有抽象来控制不同程度的安全/纯度的有效计算,IO monad 应该是最后的手段(但你不能完全避免它)。
在您的示例中,我认为您应该坚持使用 monad 变压器或定制的 monad,其作用与使用变压器组合它们相同。我从来没有写过自定义 monad(还),但我已经使用了很多 monad 转换器(我自己的代码,不在工作中),不要太担心它们,使用它们,它并没有你想象的那么糟糕。
您是否看过使用 monad 转换器的 Real World Haskell 章节?
I think this is a misconception, only the IO monad is not pure. monads like Write/T/Reader/T/State/T/ST monads are purely functional still. You can write a pure function which uses any of these monads internally like this completely useless example:
All this is doing is threading/plumbing the state implicitly, what you would do yourself by hand explicitly, the do-notation here just gives you some nice syntactic sugar to make it look imperative. You can't do any IO actions here, you can't call any foreign functions. ST monad lets you have real mutable references in a local scope while having a pure function interface and, you can't do any IO actions in there it's purely functional still.
You can't avoid some IO actions but you don't want to fall back to IO for everything because that is where anything can go, missiles can be launched, you've got no control. Haskell has abstractions to control effectful computations at varying degrees of safety/purity, IO monad should be the last resort (but you can't avoid it completely).
In your example I think you should stick to using monad transformers or a custom made monad that does the same as composing them with transformers. I've never written a custom monad (yet) but I've used monad transformers quite a bit (my own code, not at work), don't worry about them so much, use them and it's not as bad as you think.
Have you seen the chapter from Real World Haskell that uses monad transformers?