甲单子是被巨资(纯)函数编程,基本上Haskell中所使用的数学结构。 不过,也有许多其它的数学结构,例如像应用性函子,强单子,或类群。 有些有更具体的,有些是更通用 。 然而,单子都更受欢迎。 这是为什么?
我想出了一种解释是,它们是通用性和特异性之间的最佳平衡点。 这意味着单子捕捉足够的假设有关数据的应用,我们通常使用的算法和我们平时的数据满足单子法律。
另一种解释可能是,Haskell提供的单子(DO-符号)语法,但不支持其他的结构,这意味着哈斯克尔程序员(因此函数式编程研究员)朝单子,其中一个更通用的或特定的(有效)函数将被直观地绘制正常工作。
我怀疑给这一个特定类型的类(不成比例的大量关注Monad
)在许多人主要是历史的偶然。 人们常常联想IO
与Monad
,虽然两个独立有用的想法( 因为是链表反转和香蕉 )。 由于IO
是神奇的(具有被执行但没有外延)和Monad
往往与相关的IO
,很容易落入约一厢情愿Monad
。
(旁白:这是有疑问的IO
甚至是一个单子做单子法律抱什么法律甚至意味着 ? IO
,即是什么,平等是指注意? 与国家单子有问题的关联 。)
如果一个类型m :: * -> *
有一个Monad
实例,你得到的功能图灵完备的组成与类型a -> mb
。 这是一个极其有用的属性。 你得到的能力,抽象各种图灵完备的控制流程,从特定的意义了。 这是一个支持任何抽象的控制流用于支持它的类型工作的最小组成的图案。
与此相比, Applicative
,例如。 在那里,你只能用相当于一个下推自动机的计算能力构图模式。 当然,这是事实,更多类型的支持比较有限权力组成。 而且这是真的,当你限制了可用的力量,你可以做更多的优化。 这两个原因是为什么Applicative
类存在,是非常有用的。 但事情可以的情况下Monad
通常,这样类型的用户可以与类型最普遍的操作可以执行。
编辑:应广大用户要求,这里所使用的一些功能Monad
类:
ifM :: Monad m => m Bool -> m a -> m a -> m a
ifM c x y = c >>= \z -> if z then x else y
whileM :: Monad m => (a -> m Bool) -> (a -> m a) -> a -> m a
whileM p step x = ifM (p x) (step x >>= whileM p step) (return x)
(*&&) :: Monad m => m Bool -> m Bool -> m Bool
x *&& y = ifM x y (return False)
(*||) :: Monad m => m Bool -> m Bool -> m Bool
x *|| y = ifM x (return True) y
notM :: Monad m => m Bool -> m Bool
notM x = x >>= return . not
结合那些做语法(或原料>>=
运算符)给你的名字结合,无限期的循环,以及完整的布尔逻辑。 这是一个众所周知的一套足以给图灵完整性原语。 注意如何将所有的功能已经取消对一元价值的工作,而不是简单的值。 所有的一元效应也必然只在必要时-只能从所选择的分支的影响ifM
被绑定到其最终值。 无论*&&
和*||
可能的情况下忽略了他们的第二个参数。 等等..
现在,这些类型签名可能不涉及每一个单子的操作数的功能,但是这只是一种认知简化。 就没有语义差别,忽视了底部,如果所有的非功能参数和结果改为() -> ma
。 这只是友好用户进行优化,认知的开销了。
现在,让我们看看会发生什么这些功能与Applicative
接口。
ifA :: Applicative f => f Bool -> f a -> f a -> f a
ifA c x y = (\c' x' y' -> if c' then x' else y') <$> c <*> x <*> y
嗯,嗯。 它得到了相同类型的签名。 但是,这里有一个真正的大问题了。 x和y的影响结合到构成的结构,而不管被选择其中之一的值无关。
whileA :: Applicative f => (a -> f Bool) -> (a -> f a) -> a -> f a
whileA p step x = ifA (p x) (whileA p step <$> step x) (pure x)
好了,好了,这似乎是它会是好的,除了一个事实,即它是一个无限循环,因为ifA
总会执行两个分支......除了这甚至不是结束。 pure x
具有式fa
。 whileA p step <$> step x
具有式f (fa)
这甚至不是一个无限循环。 这是一个编译错误。 让我们再试一次..
whileA :: Applicative f => (a -> f Bool) -> (a -> f a) -> a -> f a
whileA p step x = ifA (p x) (whileA p step <*> step x) (pure x)
好了拍摄。 甚至不要走到这一步。 whileA p step
的类型为a -> fa
。 如果您尝试使用它作为第一个参数<*>
它抓住了Applicative
的顶部类型的构造,这是实例(->)
不f
。 是啊,这是不是要去工作无论是。
事实上,从我的唯一功能Monad
例子都将与合作Applicative
接口notM
。 这种特定的功能工作得很好,只有一个Functor
接口,其实。 其余的部分? 他们失败。
当然,这是可以预料到的是,你可以使用编写代码Monad
界面,你可以不与Applicative
接口。 它确实更强大,毕竟。 但有趣的是你失去了什么。 你输了,编写改变他们根据他们的意见有什么影响功能的能力。 也就是说,你失去编写组成与类型功能的某些控制流模式的能力a -> fb
。
图灵完备的成分到底是什么让Monad
界面有趣。 如果没有让图灵完备的成分,就不可能为你,程序员,撰写一起IO
在没有预先包装很好地为你的任何特定的控制流的行为。 这是一种你可以使用的事实Monad
原语来表达所做的任何控制流IO
类型在Haskell管理IO问题的可行方法。
更多类型的不仅仅是IO
具有语义有效的Monad
接口。 和它发生Haskell有在整个界面的语言设施,以抽象的。 由于这些因素, Monad
是一个宝贵的课堂,为提供情况可能时。 这样做可以让你访问提供与单子类型的工作,不管具体是什么类型所有现有的抽象功能。
所以,如果哈斯克尔程序员似乎总是关心Monad
实例的类型,这是因为它是一个可以提供的最一般,有用的实例。
首先,我认为这是不太真实的单子是比什么都更受欢迎; 这两个函子与含半幺群有很多情况下是没有单子。 但他们都非常具体; 算符提供映射,含半幺群串联。 应用性是我能想到的是,一类可能是未充分利用给予其相当大的权力,主要是因为其是一个相对较新的除了语言。
但是,是的,单子都非常受欢迎。 其中一部分是做记号; 很多幺的提供单子实例,仅仅附加值到正在运行的储液器(实质上是一个隐式作家)。 在闪耀的HTML库是一个很好的例子。 究其原因,我认为是类型签名的功率(>>=) :: Monad m => ma -> (a -> mb) -> mb
。 虽然FMAP和mappend是有用的,他们所能做的就是相当严格约束。 绑定,不过,可以表达各种各样的事情。 这是当然,册封在IO单子,流之前,也许是最好的纯功能性的方法来IO和FRP(他们身边仍然有用简单的任务,并定义组件)。 但它也提供隐式状态(读/写/ ST),其可避免一些非常繁琐的变量传递。 各种状态的单子,特别是,因为它们提供了保证该状态是单线程的,允许可变结构在纯的(非IO)码融合前是重要的。 但是,绑定有一些更特殊的用途,如扁平化嵌套数据结构(该List和Set单子),这两者都是在自己的位置非常有用(和我平时看到他们使用的脱糖,调用liftM或(>> =)明确,所以它是不会做记号的问题)。 因此,尽管函子与含半幺群(以及比较少见可折叠,另类,Traversable的,和其他人)提供标准化接口,以一个相当简单的功能,单子的绑定是相当多的灵活性。
总之,我认为所有的原因有一定的作用; 单子的普及是由于历史的偶然(做记号和应用型的后期定义)和他们的力量和通用性(相对于仿函数,类群等)和可理解性(相对于箭头)相结合的组合。
那么,首先让我来解释一下单子的作用是:单子是非常强大的,但在一定意义上:您可以使用一个单子几乎表达什么。 哈斯克尔的语言没有之类的动作循环,异常,基因突变,后藤等单子可以在语言中表达(所以它们不是特殊),使所有这些到达的。
有一个积极的和消极的一面,以这样的:这是肯定的,你可以表达你知道,从命令式编程和他们的一大堆你不这样做所有的控制结构。 我最近刚刚开发出的单子,让你在一个稍微改变背景中间的某个地方重新输入计算。 这样,你可以运行一个计算,如果失败,你只是稍微调整值再试一次。 此外单子的行动是一流的,这就是你如何建立之类的东西循环或异常处理。 虽然while
是在HaskellÇ原始它实际上只是一个普通的功能 。
消极的一面是,单子给你几乎没有任何保证。 他们是如此强大,你被允许做任何你想要的,简单地说。 换句话说,就像你从命令式语言知道你可能很难通过看它推理代码。
更一般的抽象是在这个意义上更普遍的,它们允许对表达的一些概念,你无法表达的单子。 但是,这只是故事的一部分。 即使单子,你可以使用一种叫做应用性的风格样式,在您使用的应用性接口从孤立的小零件组成你的程序。 这样做的好处是,你可以通过看它推理的代码,你可以开发组件,而不需要注意你的系统的其余部分。
单子因特殊do
记号,它可以让你写一个函数式语言命令式程序。 单子是可以让你从拼接小,可重复使用的组件(它们本身是必要的程序)一起势在必行计划抽象。 Monad的变压器是特殊的,因为它们代表加强与新功能的命令式语言。