有没有人在野外遇到过Monad Transformer?

在我的业务领域 – 一家金融机构的后台IT部门 – 软件组件周围进行全局configuration,logging进度,出现某种error handling/计算短路是非常常见的事情。可以很好地用Haskell中的Reader,Writer,Maybe-monads等来模拟,并与monad变换器组合在一起。

但似乎有一些缺点:monad变换器背后的概念是相当棘手和难以理解的,monad变换器导致非常复杂的types签名,并造成一些性能损失。

所以我想知道:在处理上面提到的这些常见任务时,单变换器是否是最佳实践?

Haskell社区在这个问题上是分裂的。

  • 约翰·休斯(John Hughes)报告说,他觉得教单子变压器比教单子更容易,他的学生用“变形金刚”的方法做得更好。

  • GHC开发人员通常会避免monad变压器,他们更愿意将他们自己的单片机集成在一起,整合他们所需要的所有function。 (今天我被告知,GHC 不会使用我三天前定义​​的monad变压器。)

对我来说,monad变压器很像无点编程(即没有命名variables的编程),这是有道理的; 毕竟,他们是在types层面上正确编程的。 我从来不喜欢无点编程,因为能够引入偶尔的名字是有用的。

我在实践中观察到的是

  • Hackage提供的monad变换器数量非常多,而且其中大部分非常简单。 这是一个经典的实例,在这个实例中,学习一个大型的库比自己的实例更难。

  • 像作家,州和环境这样的Monad是如此简单,以至于我看不到Monad变压器有多大的好处。

  • monad变压器闪耀在模块化和重用。 Liang,Hudak和Jones在他们的里程碑式的“Monad Transformers and Modular Interpreters”中expression了这个特性 。

在处理上述这些常见任务时,monad变压器是否是最佳实践?

我会说不 如果monad变压器最佳实践,那么您可以通过以不同方式组合和重用monad变压器来创build相关抽象的产品线 。 在这样的情况下,你可能会开发一些对你的问题域很重要的monad变换器(比如被GHC拒绝的那个),并且你(a)以多种方式组成它们; (b)为大多数变压器实现大量重用; (c)在每个monad变压器中封装了一些不重要的东西。

我被拒绝接受GHC的单子变压器没有达到上述标准(a)/(b)/(c)。

monad变换器背后的概念非常棘手,难以理解,monad变换器导致非常复杂的types签名

我认为这有些夸张:

  • 要使用一个特定的Monad变压器堆栈,不会比普通的Monad更难使用。 只要想想layers \ stacks,你会没事的。 几乎总是不需要多次提升纯函数(或特定的IO操作)。
  • 正如前面提到的,已经将你的Monad堆栈隐藏在一个新types中,使用泛化派生并将数据构造函数隐藏在模块中。
  • 尽量不要在函数types签名中使用特定的Monad堆栈,像MonadIO,MonadReader和MonadState(使用在Haskell 2010中标准化的灵活的上下文扩展)编写Monad类类的通用代码。
  • 使用像fclabels这样的库来减less访问Monad中logging部分的样板操作。

Monad变压器不是你唯一的select,你可以写一个自定义的Monad,使用Monad的延续。 你在IO(全局),ST(本地和受控,没有IO动作),MVar(同步),TVar(事务)中有可变的引用/数组。

我听说Monad变换器的潜在效率问题可以通过添加INLINE pragmas来绑定/返回mtl / transformers库的源代码来缓解。

我最近在F#的上下文中“monad”组件。 我写了一个强烈依赖状态monad的DSL:所有组件依赖状态monad:分析器(基于状态monad的分析器monad),variables匹配表(多于一个用于内部types),标识符查找表。 而且,这些组件一起工作,他们依靠相同的状态monad。 因此,有一个国家组成的概念,汇集了不同的地方国家,以及国家访问者的概念,给每个algorithm本国的知名度。

最初,这个devise真的是“只是一个大国家单子”。 但是,我开始需要那些只有当地生活的国家,但仍然处于“持久”状态(所有这些国家都由国家单位pipe理)。 为此,我确实需要引入国家单体变压器,以增强国家和使国家单位一起适应。 我还加了一个变压器,可以在状态单声道和延续状态单声道之间自由移动,但我并没有打扰到使用它。

因此,回答这个问题:是的,monad变形金刚存在于“野外”。 然而,我会坚决反对使用它们“开箱即用”。 用简单的构build块来编写应用程序,在模块之间使用小巧的手工桥接,如果你最终使用的是像monad变压器那样的东西,那真是太好了。 不要从那里开始。

而关于types签名:我已经把这种types的编程看作是非常类似于打下眼罩的棋(我不是棋手):你的技能水平需要达到你“看到”你的function和types拼凑在一起。 types签名大多最终是一个分心,除非你明确地想要为安全原因添加types约束(或者因为编译器迫使你给它们如F#logging)。

我觉得这是一种误解,只有IO单子不纯。 像Write / T / Reader / T / State / T / ST monad这样的monad仍然是纯粹的function。

在我看来,对于纯粹/非纯粹的术语来说,似乎有不止一个概念。 你的定义“IO = unpure,everything else = pure”听起来类似于Peyton-Jones在“Taming effects”中所说的( http://ulf.wiger.net/weblog/2008/02/29/peyton-jones-taming – 影响下一个大挑战/ )。 另一方面,现实世界中的Haskell(在Monad Transformer章节的最后几页中)将纯函数与一般函数进行了对比,认为两个世界都需要不同的库。 顺便说一句,人们可以争辩说,IO也是纯粹的,它的副作用被封装在一个types为RealWorld – >(a,RealWorld)的状态函数中。 毕竟,Haskell称自己是一个纯粹的函数式语言(包括IO,我假设:-))。

我的问题不是关于理论上可以做什么,而是关于从软件工程angular度certificate有用的更多。 Monad变换器允许模块化的效果(和一般的抽象),但方向编程应该是前往?

当我学习monads时,我使用一堆StateT ContT IO构build了一个应用程序来创build一个离散事件模拟库; 这些延续被用来存储一元线程,StateT持有可运行的线程队列以及用于等待各种事件的暂停线程的其他队列。 它工作得很好。 我不知道如何编写一个新types的包装Monad实例,所以我只是把它作为一个types的同义词,工作得很好。

这些天我可能会从头开始自己的monad。 然而每当我这样做的时候,我发现自己在看“Monads的所有”和MTL的来源,以提醒我绑定操作是什么样子,所以从某种意义上说,我仍然在考虑MTL栈,即使结果是一个自定义monad。

因此,像日志或configuration这样的东西往往是相当全球的,你会build议把IO monad? 从看(一个公认非常有限的)例子,我认为Haskell代码往往是纯粹的(即不是一元)或在IO monad。 或者这是一种误解?

我觉得这是一种误解,只有IO单子不纯。 像Write / T / Reader / T / State / T / ST monad这样的monad仍然是纯粹的function。 你可以编写一个纯粹的函数,使用这些monad内部的任何一个像这个完全没用的例子:

 foo :: Int -> Int foo seed = flip execState seed $ do modify $ (+) 3 modify $ (+) 4 modify $ (-) 2 

所有这一切都是隐式地对状态进行线程化或pipe理,你将自己手动明确地做什么,这里的符号只是给你一些很好的语法糖,使它看起来势在必行。 你不能在这里做任何IO动作,你不能调用任何外部函数。 ST monad让你在一个本地范围内有一个真正的可变引用,同时拥有一个纯函数接口,并且你不能在这里执行任何IO操作,它仍然是纯粹的function。

你不能避免一些IO动作,但你不想回到IO,因为这是任何事情都可以去的地方,导弹可以发射,你无法控制。 Haskell抽象控制有效的计算在不同程度的安全/纯度,IO monad应该是最后的手段(但是你不能完全避免它)。

在你的例子中,我认为你应该坚持使用monad变压器或一个定制的monad,它们和变压器一样。 我从来没有写过一个自定义monad(但是),但我已经使用monad变换器(我自己的代码,不工作),不要担心他们这么多,使用它们,并没有你想象的那么糟糕。

你有没有看到真正的世界哈斯克尔使用monad变压器的章节 ?