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

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


当前回答

遵循您简短、简洁、实用的指示:

理解monad最简单的方法是在上下文中应用/组合函数。假设你有两个计算,它们都可以看作是两个数学函数f和g。

f取一个String并生成另一个String(取前两个字母)g获取一个String并生成另一个String(大写转换)

因此,在任何语言中,“取前两个字母并将其转换为大写”的转换都会写成g(f(“某个字符串”))。因此,在纯完美函数的世界中,合成只是:先做一件事,然后再做另一件事。

但假设我们生活在一个功能可能失败的世界中。例如:输入字符串可能有一个字符长,因此f将失败。所以在这种情况下

f获取一个String并生成一个String或Nothing。g仅在f未失败时生成字符串。否则,将不生成任何内容

所以现在,g(f(“somestring”))需要一些额外的检查:“计算f,如果它失败,那么g应该返回Nothing,否则计算g”

此思想可应用于任何参数化类型,如下所示:

让Context[Sometype]是Context中Sometype的计算。考虑功能

f: :AnyType->上下文[Sometype]g: :某些类型->上下文[AnyOtherType]

合成g(f())应该读作“compute f。在这个上下文中,做一些额外的计算,然后计算g,如果它在上下文中有意义”

其他回答

除了上面出色的答案之外,让我为您提供以下文章的链接(由Patrick Thomson撰写),该文章通过将概念与JavaScript库jQuery(及其使用“方法链接”来操作DOM的方式)相关联来解释monads:jQuery是Monad

jQuery文档本身并没有提到术语“monad”,而是谈到了可能更熟悉的“构建器模式”。这并不能改变一个事实,那就是你有一个合适的monad,也许你甚至没有意识到它。

公主对F#计算表达式的解释帮助了我,尽管我仍然不能说我真的理解了。

编辑:这个系列-用javascript解释monad-对我来说是一个“打破平衡”的系列。

http://blog.jcoglan.com/2011/03/05/translation-from-haskell-to-javascript-of-selected-portions-of-the-best-introduction-to-monads-ive-ever-read/http://blog.jcoglan.com/2011/03/06/monad-syntax-for-javascript/http://blog.jcoglan.com/2011/03/11/promises-are-the-monad-of-asynchronous-programming/

我认为理解单子是一件让你毛骨悚然的事。从这个意义上说,尽可能多地阅读“教程”是一个好主意,但通常奇怪的东西(不熟悉的语言或语法)会让你的大脑无法专注于基本内容。

有些事情我很难理解:

基于规则的解释对我来说从未奏效,因为大多数实际示例实际上需要的不仅仅是返回/绑定。此外,称之为规则也无济于事。这更像是“有些东西有共同点,我们把它们称为‘单子’,把共同点称为‘规则’”。Return(a->M<a>)和Bind(M<a>->(a->M<b>)->M<b>)很好,但我永远无法理解Bind如何从M<a>中提取a,以便将其传递给a->M<b>。我不认为我在任何地方读过(也许这对其他人来说都很明显),Return(M<a>->a)的反面必须存在于monad内部,它只是不需要暴露。

http://code.google.com/p/monad-tutorial/正是为了解决这个问题而进行的工作。

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

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

我最喜欢的Monad教程:

http://www.haskell.org/haskellwiki/All_About_Monads

(在谷歌搜索“monad教程”的17万次点击中!)

@斯图:monads的目的是允许您将(通常)顺序语义添加到纯代码中;您甚至可以组合Monad(使用Monad Transformers)并获得更有趣和复杂的组合语义,例如,带有错误处理的解析、共享状态和日志记录。所有这些在纯代码中都是可能的,monad只允许您将其抽象并在模块化库中重用(在编程中总是很好的),并提供方便的语法使其看起来势在必行。

Haskell已经有了运算符重载[1]:它使用类型类的方式与使用Java或C#中的接口的方式非常相似,但Haskell恰好也允许使用非字母数字标记(如+&&和>)作为中缀标识符。如果您的意思是“重载分号”[2],那么在您看来这只是运算符重载。“重载分号”听起来像是黑魔法,自找麻烦(想象一下有进取心的Perl黑客听到了这个想法),但关键是没有monad就没有分号,因为纯函数代码不需要或不允许显式排序。

这一切听起来比实际情况要复杂得多。sigfpe的文章很酷,但使用了Haskell来解释它,这有点无法打破理解Haskell到grok Monads和理解Monads到grok Haskell的鸡和蛋的问题。

[1] 这是与monad不同的问题,但monad使用Haskell的运算符重载特性。

[2] 这也是一个过度简化,因为链接一元操作的运算符是>>=(发音为“bind”),但有语法糖(“do”)允许您使用大括号和分号和/或缩进和换行。