这些天有很多关于单子的讨论.我已经阅读了一些文章/博客文章,但我不能用他们的例子来完全掌握这个概念.原因是monad是一个函数式语言概念,因此这些例子都是我没有使用过的语言(因为我没有深入使用过函数式语言).我无法深入掌握语法以完全遵循这些文章......但我可以告诉它有一些值得理解的东西.
但是,我非常了解C#,包括lambda表达式和其他功能特性.我知道C#只有一部分功能特性,所以monad不能用C#表示.
但是,肯定有可能传达这个概念吗?至少我希望如此.也许您可以将C#示例作为基础,然后描述C#开发人员希望他可以从那里做什么,但不能,因为该语言缺乏函数式编程功能.这太棒了,因为它会传达monad的意图和好处.所以这就是我的问题:你可以给一个C#3开发人员提供monad的最佳解释是什么?
谢谢!
(编辑:顺便说一下,我知道SO上至少有3个"什么是monad"问题.然而,我遇到了同样的问题......所以这个问题需要imo,因为C#-developer焦点.谢谢.)
你在整天编程中所做的大部分工作是将一些功能组合在一起,以便从中构建更大的功能.通常,您不仅可以在工具箱中使用函数,还可以使用其他函数,例如运算符,变量赋值等,但通常您的程序会将大量"计算"组合到更大的计算中,这些计算将进一步组合在一起.
monad是一种"计算结合"的方法.
通常,将两个计算组合在一起的最基本的"运算符"是;
:
a; b
当你这么说时,你的意思是"先做a
,然后做b
".结果a; b
基本上是一个可以与更多东西组合在一起的计算.这是一个简单的monad,它是一种将小型计算与大型计算相结合的方法.该;
说"我们该做的左侧,再做右侧的事".
在面向对象语言中可以看作monad的另一件事是.
.经常你会发现这样的事情:
a.b().c().d()
的.
基本意思是"对左侧的计算,然后调用上的这个结果正确的方法".这是将函数/计算组合在一起的另一种方法,比复杂一点;
.将事物链接在一起的概念.
是一个monad,因为它是将两个计算组合在一起进行新计算的一种方式.
另一个相当常见的monad,没有特殊的语法,就是这种模式:
rv = socket.bind(address, port); if (rv == -1) return -1; rv = socket.connect(...); if (rv == -1) return -1; rv = socket.send(...); if (rv == -1) return -1;
返回值-1表示失败,但是没有真正的方法可以抽象出这个错误检查,即使您有许多需要以这种方式组合的API调用.这基本上只是另一个monad,它结合了规则的函数调用"如果左边的函数返回-1,自己返回-1,否则调用右边的函数".如果我们有一个操作员>>=
做了这件事,我们可以简单地写:
socket.bind(...) >>= socket.connect(...) >>= socket.send(...)
它会使事情更具可读性,并有助于抽象出我们组合函数的特殊方式,这样我们就不需要一遍又一遍地重复.
还有很多方法可以组合作为一般模式有用的函数/计算,并且可以在monad中进行抽象,使monad的用户能够编写更加简洁明了的代码,因为所有的簿记和管理都是如此.使用过的函数在monad中完成.
例如,上面的内容>>=
可以扩展为"执行错误检查,然后在我们获得的套接字上调用右侧作为输入",这样我们就不需要显式指定socket
很多次了:
new socket() >>= bind(...) >>= connect(...) >>= send(...);
正式定义有点复杂,因为您必须担心如何将一个函数的结果作为下一个函数的输入,如果该函数需要该输入,并且因为您希望确保您组合的函数适合你尝试将它们组合在你的monad中的方式.但基本概念就是你将形式组合在一起的不同方式形式化.
我发布这个问题已经有一年了.在发布之后,我钻研了Haskell几个月.我非常喜欢它,但是当我准备好钻研莫纳德时,我把它放在一边.我回去工作,专注于我的项目所需的技术.
昨晚,我来了,重新阅读了这些回复.更重要的是,我重读了具体的C#示例中的文本注释的布赖恩·贝克曼视频有人提到以上.它是如此完全清晰和有启发性,我决定直接在这里发布它.
正因为如此评论,而不是只做我觉得我明白究竟单子是什么......我意识到我其实C#编写的一些事情,是单子......或者至少非常接近,努力解决同样的问题.
所以,这里的评论-这是所有直接引用此评论的西尔万:
这很酷.虽然这有点抽象.我可以想象,由于缺乏真实的例子,不知道monad已经被混淆的人.
所以让我试着遵守,只是要非常清楚我会在C#中做一个例子,即使它看起来很难看.我将在最后添加等效的Haskell,并向您展示很酷的Haskell语法糖,这是IMO,monad真正开始变得有用的地方.
好吧,所以最简单的Monads之一在Haskell被称为"Maybe monad".在C#中,调用Maybe类型
Nullable
.它基本上是一个很小的类,它只是封装了一个有效且有值的值的概念,或者是"null"并且没有值.坚持在monad中用于组合这种类型的值的一个有用的东西是失败的概念.也就是说,我们希望能够查看多个可空值,并
null
在其中任何一个值为空时立即返回.例如,如果您在字典或其他内容中查找大量键,并且最后您想要处理所有结果并以某种方式组合它们,这可能很有用,但如果任何键不在字典中,你想回归null
整个事情.手动检查每个查找null
和返回是很繁琐的 ,所以我们可以隐藏绑定操作符内的这个检查(这是monad的一点,我们隐藏绑定操作符中的簿记,使代码更容易使用,因为我们可以忘记细节).这是激励整个事情的程序(我将定义
Bind
后者,这只是为了向您展示为什么它很好).class Program { static Nullablef(){ return 4; } static Nullable g(){ return 7; } static Nullable h(){ return 9; } static void Main(string[] args) { Nullable z = f().Bind( fval => g().Bind( gval => h().Bind( hval => new Nullable ( fval + gval + hval )))); Console.WriteLine( "z = {0}", z.HasValue ? z.Value.ToString() : "null" ); Console.WriteLine("Press any key to continue..."); Console.ReadKey(); } } 现在,暂时忽略已经支持
Nullable
在C#中执行此操作(您可以将可空的int添加到一起,如果其中任何一个为null,则为null).让我们假装没有这样的功能,它只是一个没有特殊魔力的用户定义的类.关键是我们可以使用Bind
函数将变量绑定到我们的Nullable
值的内容,然后假装没有什么奇怪的事情发生,并像普通的int一样使用它们,只需将它们加在一起即可.我们在为空的末包装的结果,而且可为空要么是空(如果有的话f
,g
或h
返回null)或将是总结的结果f
,g
和h
在一起.(这类似于我们如何将数据库中的行绑定到LINQ中的变量,并对其进行操作,安全地知道Bind
操作符将确保变量只会传递有效的行值).你可以用这个玩和改变任何
f
,g
和h
返回NULL,你会看到,整个事情将返回null.很明显,绑定运算符必须为我们执行此检查,并且如果遇到空值则保留返回null,否则将
Nullable
结构内部的值传递给lambda.这是
Bind
运营商:public static Nullable Bind( this Nullable a, Func> f ) where B : struct where A : struct { return a.HasValue ? f(a.Value) : null; }这里的类型就像在视频中一样.它需要一个
M a
(Nullable
对于这种情况下在C#语法),和一个功能从a
到M b
(Func>
在C#语法),并返回一个M b
(Nullable
).代码只检查nullable是否包含一个值,如果是,则将其提取并传递给函数,否则它只返回null.这意味着
Bind
操作员将为我们处理所有空检查逻辑.当且仅当我们调用Bind
的值为非null时,该值将"传递"到lambda函数,否则我们提前拯救并且整个表达式为null.这允许我们编写使用的单子是完全免费的这个空检查行为的代码,我们只是使用Bind
并获得绑定到一元价值内值的变量(fval
,gval
并hval
在示例代码),我们可以使用他们的安全在Bind
传递它们之前将负责检查它们的知识.还有其他一些你可以用monad做的事情的例子.例如,您可以让
Bind
操作员处理输入的字符流,并使用它来编写解析器组合器.然后,每个解析器组合器可以完全忘记诸如反向跟踪,解析器失败等事情,并且只需将较小的解析器组合在一起,就像事情永远不会出错一样,安全地知道聪明的实现可以Bind
排除所有逻辑背后的逻辑.困难的一点.然后可能有人将记录添加到monad中,但使用monad的代码不会改变,因为所有的魔法都发生在Bind
运算符的定义中,其余的代码都没有改变.最后,这里是Haskell中相同代码的实现(
--
开始注释行).-- Here's the data type, it's either nothing, or "Just" a value -- this is in the standard library data Maybe a = Nothing | Just a -- The bind operator for Nothing Nothing >>= f = Nothing -- The bind operator for Just x Just x >>= f = f x -- the "unit", called "return" return = Just -- The sample code using the lambda syntax -- that Brian showed z = f >>= ( \fval -> g >>= ( \gval -> h >>= ( \hval -> return (fval+gval+hval ) ) ) ) -- The following is exactly the same as the three lines above z2 = do fval <- f gval <- g hval <- h return (fval+gval+hval)
正如你可以看到最后的好
do
记法使它看起来像直截了当的代码.确实这是设计上的.Monads可以用来封装命令式编程中的所有有用的东西(可变状态,IO等),并使用这种类似命令式的语法,但在幕后,它只是monad和bind操作符的巧妙实现!很酷的是你可以通过实现>>=
和实现自己的monadreturn
.如果你这样做,那些monad也可以使用这种do
符号,这意味着你只需要定义两个函数就可以编写自己的小语言了!
monad基本上是延迟处理.如果你试图用不允许它们的语言编写具有副作用的代码(例如I/O),并且只允许纯计算,那么一个躲闪就是说,"好吧,我知道你不会做副作用对我来说,但是你可以计算一下如果你做了会发生什么吗?"
这有点作弊.
现在,这个解释将帮助你理解monad的全局意图,但魔鬼在细节中.你究竟如何计算后果?有时,它不漂亮.
概述用于命令式编程的人的方法的最佳方式是说它将你置于一个DSL中,其中使用在语法上看起来像你在monad之外习惯的操作来代替构建一个可以执行的函数你想要什么(例如)写入输出文件.几乎(但不是真的)好像你在字符串中构建代码以便稍后进行评估.