在最近简要回顾了Haskell之后,对于monad本质上是什么,有什么简单、简洁、实用的解释?

我发现,我遇到的大多数解释都很难理解,而且缺乏实际细节。


当前回答

世界需要的是另一篇monad博客文章,但我认为这对识别野外现存的monad很有用。

单子是分形

上面是一个叫做Sierpinski三角形的分形,这是我唯一记得画的分形。分形是与上述三角形相似的自相似结构,其中部分与整体相似(在这种情况下,正好是母三角形比例的一半)。单子是分形。给定一个一元数据结构,它的值可以组合成数据结构的另一个值。这就是为什么它对编程有用,这也是为什么它在许多情况下都会出现。

其他回答

在Scala的上下文中,您会发现以下是最简单的定义。基本上,flatMap(或bind)是“关联”的,并且存在一个标识。

trait M[+A] {
  def flatMap[B](f: A => M[B]): M[B] // AKA bind

  // Pseudo Meta Code
  def isValidMonad: Boolean = {
    // for every parameter the following holds
    def isAssociativeOn[X, Y, Z](x: M[X], f: X => M[Y], g: Y => M[Z]): Boolean =
      x.flatMap(f).flatMap(g) == x.flatMap(f(_).flatMap(g))

    // for every parameter X and x, there exists an id
    // such that the following holds
    def isAnIdentity[X](x: M[X], id: X => M[X]): Boolean =
      x.flatMap(id) == x
  }
}

E.g.

// These could be any functions
val f: Int => Option[String] = number => if (number == 7) Some("hello") else None
val g: String => Option[Double] = string => Some(3.14)

// Observe these are identical. Since Option is a Monad 
// they will always be identical no matter what the functions are
scala> Some(7).flatMap(f).flatMap(g)
res211: Option[Double] = Some(3.14)

scala> Some(7).flatMap(f(_).flatMap(g))
res212: Option[Double] = Some(3.14)


// As Option is a Monad, there exists an identity:
val id: Int => Option[Int] = x => Some(x)

// Observe these are identical
scala> Some(7).flatMap(id)
res213: Option[Int] = Some(7)

scala> Some(7)
res214: Some[Int] = Some(7)

注:严格地说,函数编程中的Monad的定义与范畴理论中的Monard的定义不同,后者是按映射和展平的顺序定义的。尽管它们在某些映射下是等价的。这个演示非常好:http://www.slideshare.net/samthemonad/monad-presentation-scala-as-a-category

Monad用于控制流,就像抽象数据类型用于数据一样。

换句话说,许多开发人员对集合、列表、字典(或哈希、或地图)和树的概念很熟悉。在这些数据类型中有许多特殊情况(例如InsertionOrderPreservingIdentityHashMap)。

然而,当面对程序“流”时,许多开发人员还没有接触到比if、switch/case、do、while、goto(grr)和(可能)闭包更多的构造。

因此,monad只是一个控制流构造。替代monad的更好短语是“控制类型”。

因此,monad具有用于控制逻辑、语句或函数的槽——数据结构中的等价物是,某些数据结构允许您添加数据,并删除数据。

例如,“if”monad:

if( clause ) then block

最简单的是有两个槽:一个子句和一个块。if monad通常用于评估子句的结果,如果不是false,则评估块。许多开发人员在学习“如果”时并没有接触到monad,而且编写有效的逻辑并不需要理解monad。

monad可能会变得更复杂,就像数据结构可能变得更复杂一样,但monad有很多大类可能具有相似的语义,但实现和语法不同。

当然,数据结构可以在单子上迭代或遍历,也可以以同样的方式进行评估。

编译器可能支持也可能不支持用户定义的monad。哈斯克尔当然知道。Ioke有一些类似的功能,尽管语言中没有使用monad一词。

根据我们所谈论的monad,“什么是monad”这个问题是错误的:

对“什么是单单体?”这个问题的简短回答是,它是内函子范畴中的单单体,或者它是一种通用数据类型,配备了满足某些定律的两个运算。这是正确的,但它并没有揭示一个重要的大局。这是因为问题是错误的。在这篇论文中,我们的目标是回答正确的问题,即“当作者谈论单子时,他们真正说的是什么?”

虽然这篇论文没有直接回答什么是单子,但它有助于理解不同背景的人谈论单子时的含义以及原因。

Monad是一个可应用的(即,你可以将二进制(因此,“n元”)函数提升到(1),并将纯值注入(2))Functor(即,可以映射到(3)的函数,即提升一元函数到(3”),它还具有展平嵌套数据类型的能力(三个概念中的每一个都遵循其相应的一组规则)。在Haskell中,这种扁平化操作称为join。

此“联接”操作的常规(通用、参数化)类型为:

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

对于任何monad m(注意,类型中的所有ms都是相同的!)。

特定的m monad定义了其特定版本的join,该版本适用于由类型m A的monadic值“携带”的任何值类型A。某些特定类型包括:

join  ::  [[a]]           -> [a]         -- for lists, or nondeterministic values
join  ::  Maybe (Maybe a) -> Maybe a     -- for Maybe, or optional values
join  ::  IO    (IO    a) -> IO    a     -- for I/O-produced values

连接操作将产生a型值的m计算的m计算转换为a型值组合的m计算。这允许将计算步骤组合成一个更大的计算。

结合“bind”(>>=)运算符的计算步骤简单地使用fmap和join,即。

(ma >>= k)  ==  join (fmap k ma)
{-
  ma        :: m a            -- `m`-computation which produces `a`-type values
  k         ::   a -> m b     --  create new `m`-computation from an `a`-type value
  fmap k ma :: m    ( m b )   -- `m`-computation of `m`-computation of `b`-type values
  (m >>= k) :: m        b     -- `m`-computation which produces `b`-type values
-}

相反,可以通过bind定义join,join mma==join(fmap id mma)==mma>>=id,其中id ma=ma——对于给定的类型m,以更方便的为准。

对于monad,do表示法及其使用代码的等效绑定,

do { x <- mx ; y <- my ; return (f x y) }        --   x :: a   ,   mx :: m a
                                                 --   y :: b   ,   my :: m b
mx >>= (\x ->                                    -- nested
            my >>= (\y ->                        --  lambda
                         return (f x y) ))       --   functions

可以读为

首先“做”mx,当它完成时,将其“结果”作为x,让我用它“做”其他事情。

在给定的do块中,绑定箭头<-右侧的每个值对于某些类型a都是m a类型,在整个do块中都是相同的monad m。

返回x是一个中立的m计算,它只产生给定的纯值x,因此将任何m计算与返回绑定都不会改变该计算。


(1) 提升A2::适用m=>(a->b->c)->m a->m b->m c

(2) 纯::适用m=>a->m a

(3) 具有fmap::函数m=>(a->b)->m a->m b

还有等效的Monad方法,

liftM2 :: Monad m => (a -> b -> c) -> m a -> m b -> m c
return :: Monad m =>  a            -> m a
liftM  :: Monad m => (a -> b)      -> m a -> m b

给定monad,其他定义可以如下

pure   a       = return a
fmap   f ma    = do { a <- ma ;            return (f a)   }
liftA2 f ma mb = do { a <- ma ; b <- mb  ; return (f a b) }
(ma >>= k)     = do { a <- ma ; b <- k a ; return  b      }

解释

当用C#/Java术语解释时,这很简单:

monad是一个接受参数并返回特殊类型的函数。这个monad返回的特殊类型也称为monad。(monad是#1和#2的组合)有一些语法糖可以使调用此函数和类型转换更容易。

实例

monad有助于使函数式程序员的生活更轻松。典型示例:Maye monad接受两个参数,一个值和一个函数。如果传递的值为null,则返回null。否则它将计算函数。如果我们需要一个特殊的返回类型,我们也可以调用这个返回类型Maybe。一个非常粗糙的实现如下所示:

object Maybe(object value, Func<object,object> function)
{
    if(value==null)
        return null;

    return function(value);
}

这在C#中是非常无用的,因为这种语言缺乏使monad有用所需的语法糖。但是monad允许您用函数式编程语言编写更简洁的代码。

通常程序员在链中调用monad,如下所示:

var x = Maybe(x, x2 => Maybe(y, y2 => Add(x2, y2)));

在本例中,只有当x和y都为非null时,才会调用Add方法,否则将返回null。

答复

回答最初的问题:monad是一个函数和一个类型。就像一个特殊接口的实现。