Monads有什么特别之处?

时间:2013-05-08 11:05:24

标签: math haskell functional-programming structure monads

monad是一种数学结构,在(纯)函数式编程中大量使用,基本上是Haskell。然而,还有许多其他数学结构可用,例如应用函子,强单子或幺半群。有些更具体,有些是more generic。然而,monads更受欢迎。那是为什么?

我提出的一个解释是,它们是通用性和特异性之间的最佳点。这意味着monads捕获关于数据的足够假设,以应用我们通常使用的算法以及我们通常满足monadic定律的数据。

另一种解释可能是Haskell为monad(do-notation)提供了语法,但没有为其他结构提供语法,这意味着Haskell程序员(以及函数式编程研究人员)直观地被用于monad,其中更通用或更具特色(高效) )功能也可以。

5 个答案:

答案 0 :(得分:29)

我怀疑对这一特定类型(Monad)的关注程度比其他许多类别高得多,主要是历史侥幸。人们经常将IOMonad相关联,尽管这两者是独立有用的想法(as are list reversal and bananas)。由于IO是神奇的(有实现但没有外延),而Monad通常与IO相关联,因此很容易陷入对Monad的神奇思考。

(旁白:IO甚至是monad是否值得怀疑。monad法律是否成立?IO的法则甚至意味着什么,即,平等是什么意思?注意problematic association with the state monad。)

答案 1 :(得分:16)

如果类型m :: * -> *具有Monad实例,则会获得类型为a -> m b的图灵完整函数组合。这是一个非常有用的财产。您可以从特定含义中抽象出各种图灵完备控制流。它是一个最小的组合模式,支持抽象任何控制流,以便使用支持它的类型。

例如,将此与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

将那些与do语法(或原始>>=运算符)组合在一起,可以为您提供名称绑定,无限循环和完整的布尔逻辑。这是一套众所周知的基元,足以赋予图灵完整性。请注意如何解除所有函数以处理monadic值,而不是简单值。所有monadic效果仅在必要时才绑定 - 只有ifM所选分支的效果才会绑定到其最终值。在可能的情况下,*&&*||都会忽略他们的第二个参数。等等..

现在,那些类型签名可能不涉及每个monadic操作数的函数,但这只是一种认知简化。如果所有非函数参数和结果都更改为() -> m a,则不存在语义差异,忽略底部。用户可以更好地优化认知开销。

现在,让我们看一下使用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的类型为f awhileA p step <$> step x的类型为f (f a)。这甚至不是无限循环。这是一个编译错误。我们再试一次..

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 -> f a。如果您尝试将其用作<*>的第一个参数,它会抓取 top 类型构造函数的Applicative实例,该(->)f,而不是{{ 1}}。是的,这也不会起作用。

事实上,我Monad示例中唯一可与Applicative界面配合使用的函数是notM。事实上,该特定功能仅适用于Functor接口。其余的部分?他们失败了。

当然可以预期您可以使用Monad接口无法使用Applicative接口编写代码。毕竟,它更加强大。但有趣的是你失去了什么。你失去了根据输入改变改变效果的功能的能力。也就是说,您将无法编写构成a -> f b类型函数的某些控制流模式。

图灵完整的合成正是使Monad界面变得有趣的原因。如果它不允许Turing-complete组合,那么程序员就不可能在任何特定的控制流程中组合IO动作,而这些控制流程并没有很好地预先打包给你。事实上,您可以使用Monad原语来表达任何控制流,这使得IO类型成为管理Haskell中IO问题的可行方法。

除了IO之外,还有许多类型具有语义上有效的Monad接口。而且Haskell的语言功能可以在整个界面上进行抽象。由于这些因素,Monad是一个有价值的类,可以在可能的情况下提供实例。通过这样做,您可以访问为使用monadic类型而提供的所有现有抽象功能,无论具体类型是什么。

因此,如果Haskell程序员似乎总是关心类型的Monad个实例,那是因为它是可以提供的最通用的实例。

答案 2 :(得分:12)

首先,我认为monad比其他任何东西都更受欢迎; Functor和Monoid都有许多不是monad的实例。但它们都非常具体; Functor提供映射,Monoid连接。应用是我能想到的一个类,鉴于其相当大的力量,它可能未被充分利用,主要是因为它是该语言的一个相对新近的补充。

但是,是的,monads非常受欢迎。其中一部分是做法;很多Monoids提供的Monad实例只是将值附加到正在运行的累加器(本质上是一个隐式编写器)。 blaze-html库就是一个很好的例子。我认为,原因是类型签名(>>=) :: Monad m => m a -> (a -> m b) -> m b的强大功能。虽然fmap和mappend很有用,但它们可以做的却相当狭窄。但是,bind可以表达各种各样的东西。当然,它是在IO monad中封装的,也许是在流和FRP之前IO的最佳纯函数方法(对于简单任务和定义组件,它们仍然有用)。但它也提供了隐式状态(Reader / Writer / ST),它可以避免一些非常繁琐的变量传递。特别是各种状态monad非常重要,因为它们可以保证状态是单线程的,在融合之前允许纯(非IO)代码中的可变结构。但是bind有一些更奇特的用法,例如展平嵌套数据结构(List和Set monads),两者都非常有用(我经常看到它们使用desugared,调用liftM或(&gt;&gt; =)明确地说,所以这不是表示法的问题)。因此,尽管Functor和Monoid(以及更为罕见的Foldable,Alternative,Traversable等)为相当简单的功能提供了标准化的界面,但Monad的绑定具有更大的灵活性。

总之,我认为你所有的理由都有一定的作用; monads的流行是由于历史事故(符号和最近的Applicative定义)和它们的力量和普遍性(相对于仿函数,幺半群等)和可理解性(相对于箭头)的组合。

答案 3 :(得分:6)

嗯,首先让我解释monad的作用是什么:Monads非常强大,但在某种意义上:你可以使用monad表达任何东西。 Haskell作为一种语言没有动作循环,异常,变异,转到等等.Monads可以在语言中表达(因此它们并不特殊)并使所有这些都可以访问。

这有一个积极和消极的方面:你可以通过命令式编程表达你所知道的所有控制结构,而不是你可以表达的一大堆控制结构,这是积极的。我刚刚开发了一个monad,它允许你在稍微改变的上下文中重新进入中间的某个计算。这样你就可以运行一个计算,如果它失败了,你只需要稍微调整一下值即可。此外,monadic动作是一流的,这就是你如何构建循环或异常处理之类的东西。虽然{H}中的while在C语言中是原始的,但它实际上只是一个常规的函数

负面的一面是,monads几乎无法保证你。它们是如此强大,以至于你被允许做任何你想做的事情,简单地说。换句话说,就像你从命令式语言中所知道的那样,通过查看代码就很难对代码进行推理。

更一般的抽象是更一般的,因为它们允许表达一些你不能表达为monad的概念。但那只是故事的一部分。即使对于monad,您也可以使用称为 applicative style 的样式,在该样式中,您可以使用应用程序界面从小的独立部分组成程序。这样做的好处是,您可以通过查看代码来推理代码,并且可以开发组件而无需关注系统的其余部分。

答案 4 :(得分:0)

Monads是特殊的,因为do表示法,它允许您用函数式语言编写命令式程序。 Monad是一种抽象,它允许您将较小的可重用组件(这些组件本身是命令性程序)拼接在一起。 Monad变形金刚是特殊的,因为它们代表了增强具有新功能的命令式语言。