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

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


当前回答

我将尝试在Haskell的背景下解释Monad。

在函数式编程中,函数组合很重要。它允许我们的程序由小的、易于阅读的函数组成。

假设我们有两个函数:g::Int->String和f::String->Bool。

我们可以做(f.g)x,这与f(gx)相同,其中x是Int值。

当进行合成/将一个函数的结果应用到另一个函数时,使类型匹配是很重要的。在上述情况下,g返回的结果类型必须与f接受的类型相同。

但有时值是在上下文中的,这使得排列类型有点不容易。(在上下文中设置值非常有用。例如,Maybe Int类型表示可能不存在的Int值,IO String类型表示由于执行某些副作用而存在的String值。)

假设我们现在有g1::Int->Maybe String和f1::String->Maybe Bool。g1和f1分别与g和f非常相似。

我们不能做(f1.g1)x或f1(g1 x),其中x是Int值。g1返回的结果类型不是f1期望的类型。

我们可以用。运算符,但现在我们不能用..组合f1和g1。。问题是我们不能直接将上下文中的值传递给期望值不在上下文中的函数。

如果我们引入一个运算符来组合g1和f1,这样我们就可以写出(f1 operator g1)x,这不是很好吗?g1返回上下文中的值。该值将脱离上下文并应用于f1。是的,我们有这样一个操作员。它是<=<。

我们还有一个>>=运算符,它为我们做了完全相同的事情,尽管语法略有不同。

我们写:g1 x>>=f1。g1 x是Maybe Int值。>>=运算符帮助将Int值从“可能不存在”上下文中取出,并将其应用于f1。f1的结果是Maybe Bool,它将是整个>>=操作的结果。

最后,为什么Monad有用?因为Monad是定义>>=运算符的类型类,与定义==和/=运算符的Eq类型类非常相似。

总之,Monad类型类定义了>>=运算符,该运算符允许我们将上下文中的值(我们称为这些monadic值)传递给不需要上下文中值的函数。将考虑上下文。

如果这里需要记住一点,那就是Monads允许在上下文中包含值的函数组合。

其他回答

http://mikehadlow.blogspot.com/2011/02/monads-in-c-8-video-of-my-ddd9-monad.html

这是你要找的视频。

用C#演示组合和对齐类型的问题,然后用C#正确实现它们。最后,他展示了F#和Haskell中相同的C#代码的外观。

在Coursera“反应式编程原理”培训中,Erik Meier将其描述为:

"Monads are return types that guide you through the happy path." -Erik Meijer

事实上,与一般人对蒙得斯的理解相反,他们与国家无关。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基本上允许回调嵌套(具有相互递归的线程状态(请忽略连字符))(以可组合(或可分解)的方式)(具有类型安全性(有时(取决于语言))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))

例如,这不是单子:

//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解析器组合器库时。(也就是说,根据我的经验)

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

我将尝试在Haskell的背景下解释Monad。

在函数式编程中,函数组合很重要。它允许我们的程序由小的、易于阅读的函数组成。

假设我们有两个函数:g::Int->String和f::String->Bool。

我们可以做(f.g)x,这与f(gx)相同,其中x是Int值。

当进行合成/将一个函数的结果应用到另一个函数时,使类型匹配是很重要的。在上述情况下,g返回的结果类型必须与f接受的类型相同。

但有时值是在上下文中的,这使得排列类型有点不容易。(在上下文中设置值非常有用。例如,Maybe Int类型表示可能不存在的Int值,IO String类型表示由于执行某些副作用而存在的String值。)

假设我们现在有g1::Int->Maybe String和f1::String->Maybe Bool。g1和f1分别与g和f非常相似。

我们不能做(f1.g1)x或f1(g1 x),其中x是Int值。g1返回的结果类型不是f1期望的类型。

我们可以用。运算符,但现在我们不能用..组合f1和g1。。问题是我们不能直接将上下文中的值传递给期望值不在上下文中的函数。

如果我们引入一个运算符来组合g1和f1,这样我们就可以写出(f1 operator g1)x,这不是很好吗?g1返回上下文中的值。该值将脱离上下文并应用于f1。是的,我们有这样一个操作员。它是<=<。

我们还有一个>>=运算符,它为我们做了完全相同的事情,尽管语法略有不同。

我们写:g1 x>>=f1。g1 x是Maybe Int值。>>=运算符帮助将Int值从“可能不存在”上下文中取出,并将其应用于f1。f1的结果是Maybe Bool,它将是整个>>=操作的结果。

最后,为什么Monad有用?因为Monad是定义>>=运算符的类型类,与定义==和/=运算符的Eq类型类非常相似。

总之,Monad类型类定义了>>=运算符,该运算符允许我们将上下文中的值(我们称为这些monadic值)传递给不需要上下文中值的函数。将考虑上下文。

如果这里需要记住一点,那就是Monads允许在上下文中包含值的函数组合。