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

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


当前回答

如果你要求对如此抽象的东西做出简洁、实用的解释,那么你只能希望得到一个抽象的答案:

a -> b

是表示从as到bs的计算的一种方式。您可以将计算链接起来,也可以将它们组合在一起:

(b -> c) -> (a -> b) -> (a -> c)

更复杂的计算需要更复杂的类型,例如:

a -> f b

是从as到bs到fs的计算类型。您还可以编写它们:

(b -> f c) -> (a -> f b) -> (a -> f c)

事实证明,这种模式无处不在,并且与上面的第一个组合具有相同的财产(结合性、右-和左-同一性)。

人们必须给这个模式起一个名字,但如果知道第一个组合被正式描述为半群体,这会有帮助吗?

“单子和圆括号一样有趣和重要”(奥列格·基斯廖夫)

其他回答

事实上,与一般人对蒙得斯的理解相反,他们与国家无关。Monads只是一种包装东西的方法,它提供了对包装好的东西进行操作而不展开的方法。

例如,您可以在Haskell中创建一个类型来包装另一个类型:

data Wrapped a = Wrap a

包装我们定义的东西

return :: a -> Wrapped a
return x = Wrap x

要在不展开的情况下执行操作,假设您有一个函数f::a->b,然后您可以执行此操作来提升该函数以作用于包装的值:

fmap :: (a -> b) -> (Wrapped a -> Wrapped b)
fmap f (Wrap x) = Wrap (f x)

这就是所有需要理解的。然而,事实证明,有一个更通用的函数来执行此提升,即bind:

bind :: (a -> Wrapped b) -> (Wrapped a -> Wrapped b)
bind f (Wrap x) = f x

bind可以比fmap做得更多,但反之亦然。实际上,fmap只能用绑定和返回来定义。因此,在定义monad时。。您给出它的类型(这里是Wrapped a),然后说明它的返回和绑定操作是如何工作的。

很酷的是,这是一个普遍的模式,它会在所有地方弹出,以纯方式封装状态只是其中之一。

有关如何使用monad来引入函数依赖关系,从而控制求值顺序(如Haskell的IO monad中所用)的好文章,请查看IOInside。

至于理解单子,不要太担心。读一些你觉得有趣的东西,如果你不马上理解,也不要担心。那就用Haskell这样的语言潜水吧。修道院就是这样一种东西,在那里,通过练习,理解慢慢地进入你的大脑,有一天你突然意识到你理解了它们。

一个非常简单的答案是:

Monad是一种抽象,它为封装值、计算新的封装值和展开封装值提供了接口。

它们在实践中的方便之处在于,它们提供了一个统一的接口,用于创建建模状态而非状态的数据类型。

必须理解Monad是一种抽象,即用于处理某种数据结构的抽象接口。然后,该接口用于构建具有一元行为的数据类型。

你可以在Ruby中的Monads中找到一个非常好且实用的介绍,第1部分:简介。

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

单子是分形

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

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      }

实际上,monad基本上允许回调嵌套(具有相互递归的线程状态(请忽略连字符))(以可组合(或可分解)的方式)(具有类型安全性(有时(取决于语言))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))

例如,这不是单子:

//JavaScript is 'Practical'
var getAllThree = 
         bind(getFirst, function(first){  
  return bind(getSecond,function(second){  
  return bind(getThird, function(third){  
    var fancyResult = // And now make do fancy 
                      // with first, second,
                      // and third 
    return RETURN(fancyResult);
  });});});  

但是monad启用了这样的代码。monad实际上是一组类型:{bind,RETURN,也许其他我不认识的人…}。这本质上是无关紧要的,实际上是不切实际的。

所以现在我可以使用它:

var fancyResultReferenceOutsideOfMonad =  
  getAllThree(someKindOfInputAcceptableToOurGetFunctionsButProbablyAString);  

//Ignore this please, throwing away types, yay JavaScript:
//  RETURN = K
//  bind = \getterFn,cb -> 
//    \in -> let(result,newState) = getterFn(in) in cb(result)(newState)

或将其分解:

var getFirstTwo = 
           bind(getFirst, function(first){  
    return bind(getSecond,function(second){  
      var fancyResult2 = // And now make do fancy 
                         // with first and second
      return RETURN(fancyResult2);
    });})
  , getAllThree = 
           bind(getFirstTwo, function(fancyResult2){  
    return bind(getThird,    function(third){  
      var fancyResult3 = // And now make do fancy 
                         // with fancyResult2,
                         // and third 
      return RETURN(fancyResult3);
    });});

或者忽略某些结果:

var getFirstTwo = 
           bind(getFirst, function(first){  
    return bind(getSecond,function(second){  
      var fancyResult2 = // And now make do fancy 
                         // with first and second
      return RETURN(fancyResult2);
    });})
  , getAllThree = 
           bind(getFirstTwo, function(____dontCare____NotGonnaUse____){  
    return bind(getThird,    function(three){  
      var fancyResult3 = // And now make do fancy 
                         // with `three` only!
      return RETURN(fancyResult3);
    });});

或者从以下内容简化一个小案例:

var getFirstTwo = 
           bind(getFirst, function(first){  
    return bind(getSecond,function(second){  
      var fancyResult2 = // And now make do fancy 
                         // with first and second
      return RETURN(fancyResult2);
    });})
  , getAllThree = 
           bind(getFirstTwo, function(_){  
    return bind(getThird,    function(three){  
      return RETURN(three);
    });});

收件人(使用“正确身份”):

var getFirstTwo = 
           bind(getFirst, function(first){  
    return bind(getSecond,function(second){  
      var fancyResult2 = // And now make do fancy 
                         // with first and second
      return RETURN(fancyResult2);
    });})
  , getAllThree = 
           bind(getFirstTwo, function(_){  
    return getThird;
    });

或者把它们挤在一起:

var getAllThree = 
           bind(getFirst, function(first_dontCareNow){  
    return bind(getSecond,function(second_dontCareNow){  
    return getThird;
    });});

这些能力的实用性并没有真正显现出来,或者变得清晰,直到你试图解决真正的棘手问题例如解析或模块/ajax/资源加载。

你能想象成千上万行indexOf/subString逻辑吗?如果频繁的解析步骤包含在小函数中呢?像字符、空格、大写字符或数字这样的函数?如果这些函数在回调中给出了结果,而不必与Regex集团和争论发生冲突?如果它们的组成/分解被很好地理解了呢?这样你就可以从下往上构建大型解析器了吗?

因此,管理嵌套回调范围的能力非常实用,尤其是在使用monadic解析器组合器库时。(也就是说,根据我的经验)

不要挂断电话:-分类理论-可能是月-莫纳德定律-哈斯克尔- !!!!