2012-01-08 67 views
40

我学习Haskell的方法我开始掌握monad概念,并开始在代码中使用已知的monad,但从设计师的角度来看,我仍然遇到难题。在面向对象中有几个规则,比如“为名称标识名词”,注意某种状态和接口......但是我无法为monad找到相应的资源。如何识别monadic设计模式?

那么如何将自然界中的一个问题确定为一元问题呢? monadic设计有什么好的设计模式?当你意识到某些代码可以更好地重构为monad时,你的方法是什么?

+8

这个问题“这个问题应该单独解决”,还有更多“这个问题应该用[某些数据类型]来解决,嘿!多么方便,[这个数据类型]是Monad的一个实例,给我吨合作的可组合性。“ – 2012-01-08 22:12:04

+0

@DanBurton:无论是面向对象,程序,功能,回溯逻辑,连接还是任何其他类型的语言,当然都可以采用任何其他类型的设计模式。 – 2012-01-10 12:55:41

回答

57

有用的经验法则是当您在上下文中看到值;单子可以看作是分层的“效果”:

  • 可能:偏袒(用途:能不能计算)
  • 或者:短路错误(用途:错误/异常处理)
  • [](列表单子):非确定性(用途:列表生成,过滤,...)
  • 州:单个可变引用(使用:状态)
  • 读卡器:共享环境(使用:变量绑定,公共信息,...)
  • 编剧: a “侧通道” 输出或积累(用途:日志,维护一个只写计数器, ...)
  • 续:非本地控制流(用途:不胜枚举)

通常情况下,你通常应该从标准Monad Transformer Library分层的单子变压器设计你的单子,它可以让你将上面的效果组合成一个单一的模式河畔。它们一起处理您可能想要使用的大部分monad。 MTL中没有包含一些额外的单子,如probabilitysupply单子。

至于开发一个新定义的类型是否是一个单子,以及它如何表现为一个,您可以通过从FunctorMonad涨认为它的一个直觉:

  • 函子可以让您用纯函数转换值。
  • 应用允许您嵌入纯数值并表达应用程序 - (<*>)可让您从嵌入式函数及其嵌入式参数转到嵌入式结果。
  • Monad允许嵌入式计算的结构取决于先前计算的

明白最简单的方法是看的join类型:

join :: (Monad m) => m (m a) -> m a 

这意味着,如果你有一个嵌入式计算,其结果是嵌入式计算,你可以创建执行该计算结果的计算。因此,您可以使用monadic效果根据以前的计算值创建新计算,并将控制流转移到该计算。

有趣的是,这可以是结构化的东西弱点 monadically:与Applicative,计算的结构是静态的(即,一个给定的Applicative计算具有不能更改基于中间值的影响的一定的结构),而与Monad它是动态的。这可以限制你可以做的优化;例如,应用解析器不如一元解析器强大(嗯,这不是strictly true,但实际上它是),但它们可以更好地优化。

注意(>>=)可以定义为

m >>= f = join (fmap f m) 

等单子可以简单地用returnjoin(假设它被定义是一个Functor;所有的单子都是合用的函子,但Haskell的类型类层次结构遗憾的是不要求这是historical reasons)。作为一个补充说明,无论他们从误导的非Haskellers中得到什么样的嗡嗡声,你可能都不应该过分关注单子。有很多代表有意义和强大模式的类型类,并不是所有的东西都最好用monad表示。 Applicative,Monoid,Foldable ......使用哪种抽象取决于你的情况。当然,仅仅因为某件事是一个monad并不意味着它不能成为其他事物;作为一个monad只是另一种类型的属性。

所以,你不应该过多地考虑“识别单子”。这些问题更像是:

  • 此代码是否可以用更简单的monadic形式表示?用哪个monad?
  • 是这种类型,我刚刚定义了monad?我可以利用单子上的标准函数编码哪些通用模式?
+3

哦,非常好。如果我没有睡觉的话,我会写出很多答案,哈哈。男人,我几乎不能跟上你们...... – 2012-01-08 18:31:29

+3

@ C.A.McCann:当有代表获得时,你怎么能睡觉? :) – ehird 2012-01-08 18:51:33

+0

@ehird +1你最近一直在开发大量的优秀答案。 – 2012-01-08 22:13:58

15

按照类型。

如果你发现你写的功能与所有这些类型的

  • (a -> b) -> YourType a -> YourType b
  • a -> YourType a
  • YourType (YourType a) -> YourType a

或所有这些类型的

  • a -> YourType a
  • YourType a -> (a -> YourType b) -> YourType b

然后YourType可以是单子。 (我说“可能”,因为功能必须服从法律的单子也。)

(请记住,您可以重新排序参数,因此如YourType a -> (a -> b) -> YourType b只是变相(a -> b) -> YourType a -> YourType b

不要看出来只为单子!如果你把所有这些类型的

  • YourType
  • YourType -> YourType -> YourType

的功能,他们遵守法律幺,你有一个幺!这也可能是有价值的。对于其他类型类也是如此,最重要的是Functor。

6

有单子的效果图:

  • 也许 - 偏袒/故障短路
  • 要么 - 错误报告/短路(如可能包含更多信息)
  • 作家 - 只写“状态”,经常登录
  • 阅读器 - 只读状态,一般环境路过
  • 国家 - 读/写状态
  • 恢复 - 可暂停计算
  • 名单 - 多成功

一旦你熟悉了这些影响其容易建立单子他们单子变压器相结合。请注意,组合一些单子需要特别的照顾(特别是Cont和具有回溯的单子)。

有一件重要的事情要注意的是没有太多单子。有一些外来的不在标准库中,例如概率monad和Cont monad的变体如Codensity。但是除非你正在做一些数学上的事情,否则你会发明(或发现)一个新的monad,但是如果你使用Haskell足够长的时间,你将会创建许多不同的标准monad组合。

编辑 - 另请注意,为了您堆叠在不同的单子单子变压器结果:

如果添加ErrorT(变压器)的作家单子,你得到这个单子Either err (log,a) - 你只能访问日志,如果你没有错误。

如果您将WriterT(transfomer)添加到Error monad,您会得到此monad (log, Either err a),它总是可以访问日志。

4

这是一种无法回答的问题,但我觉得反正说很重要。 只问! StackOverflow,/ r/haskell和#haskell irc频道都是获得智能人员快速反馈的好地方。如果你正在研究一个问题,并且你怀疑有一些monadic的魔法可以使它更容易,那就问问! Haskell社区喜欢解决问题,并且是非常友好的。

不要误会,我不鼓励你永远不要为自己学习。恰恰相反,与Haskell社区互动是其中一种学习方式。 LYAHRWH,2个免费在线提供的Haskell书籍,也强烈推荐。

呵呵,别忘了玩,玩,玩!当你使用monadic代码玩耍时,你会开始感觉到“形状”monad有什么样的感觉,以及monadic combinators是否有用。如果你正在推出自己的monad,那么通常类型系统会引导你一个明显的,简单的解决方案。但说实话,你应该很少需要推出你自己的Monad实例,因为Haskell库提供了其他答案者提到的大量有用的东西。