当前位置:  开发笔记 > 编程语言 > 正文

帮助C#开发人员理解:什么是monad?

如何解决《帮助C#开发人员理解:什么是monad?》经验,为你挑选了3个好方法。

这些天有很多关于单子的讨论.我已经阅读了一些文章/博客文章,但我不能用他们的例子来完全掌握这个概念.原因是monad是一个函数式语言概念,因此这些例子都是我没有使用过的语言(因为我没有深入使用过函数式语言).我无法深入掌握语法以完全遵循这些文章......但我可以告诉它有一些值得理解的东西.

但是,我非常了解C#,包括lambda表达式和其他功能特性.我知道C#只有一部分功能特性,所以monad不能用C#表示.

但是,肯定有可能传达这个概念吗?至少我希望如此.也许您可以将C#示例作为基础,然后描述C#开发人员希望他可以从那里做什么,但不能,因为该语言缺乏函数式编程功能.这太棒了,因为它会传达monad的意图和好处.所以这就是我的问题:你可以给一个C#3开发人员提供monad的最佳解释是什么?

谢谢!

(编辑:顺便说一下,我知道SO上至少有3个"什么是monad"问题.然而,我遇到了同样的问题......所以这个问题需要imo,因为C#-developer焦点.谢谢.)



1> sth..:

你在整天编程中所做的大部分工作是将一些功能组合在一起,以便从中构建更大的功能.通常,您不仅可以在工具箱中使用函数,还可以使用其他函数,例如运算符,变量赋值等,但通常您的程序会将大量"计算"组合到更大的计算中,这些计算将进一步组合在一起.

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中的方式.但基本概念就是你将形式组合在一起的不同方式形式化.


很棒的答案!我将引用Oliver Steele的一句话,试图将Monads与运算符重载C++或C#相关联:Monads允许你重载';' 运营商.
@JörgWMittag之前我读过这句话,但听起来有点过于胡说八道.现在我确实理解了monad并阅读了关于';'的解释 是一个,我明白了.但我认为这对大多数势在必行的开发人员来说真是一个非理性的陈述.';' 不会被视为操作员而不是大多数人.
你确定你知道monad是什么吗?Monads不是"函数"或计算,monad有规则.

2> Charlie Flow..:

我发布这个问题已经有一年了.在发布之后,我钻研了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 Nullable f(){ 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,gh返回null)或将是总结的结果f,gh在一起.(这类似于我们如何将数据库中的行绑定到LINQ中的变量,并对其进行操作,安全地知道Bind操作符将确保变量只会传递有效的行值).

你可以用这个玩和改变任何f,gh返回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#语法),和一个功能从aM b(Func>在C#语法),并返回一个M b (Nullable).

代码只检查nullable是否包含一个值,如果是,则将其提取并传递给函数,否则它只返回null.这意味着Bind操作员将为我们处理所有空检查逻辑.当且仅当我们调用 Bind的值为非null时,该值将"传递"到lambda函数,否则我们提前拯救并且整个表达式为null.这允许我们编写使用的单子是完全免费的这个空检查行为的代码,我们只是使用Bind并获得绑定到一元价值内值的变量(fval, gvalhval在示例代码),我们可以使用他们的安全在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操作符的巧妙实现!很酷的是你可以通过实现>>=和实现自己的monad return.如果你这样做,那些monad也可以使用这种do符号,这意味着你只需要定义两个函数就可以编写自己的小语言了!


我个人更喜欢F#版的monad,但在任何一种情况下它们都很棒.
感谢您回来并更新您的帖子.这些后续工作可以帮助那些正在研究某个特定领域的程序员真正理解其他程序员最终如何看待这个区域,而不是仅仅依靠"我如何使用x技术"来实现这一目标.你大人!

3> MarkusQ..:

monad基本上是延迟处理.如果你试图用不允许它们的语言编写具有副作用的代码(例如I/O),并且只允许纯计算,那么一个躲闪就是说,"好吧,我知道你不会做副作用对我来说,但是你可以计算一下如果你做了会发生什么吗?"

这有点作弊.

现在,这个解释将帮助你理解monad的全局意图,但魔鬼在细节中.你究竟如何计算后果?有时,它不漂亮.

概述用于命令式编程的人的方法的最佳方式是说它将你置于一个DSL中,其中使用在语法上看起来像你在monad之外习惯的操作来代替构建一个可以执行的函数你想要什么(例如)写入输出文件.几乎(但不是真的)好像你在字符串中构建代码以便稍后进行评估.


嗯,Monad _can_可以用于延迟处理和封装副作用函数,实际上这是它在Haskell中的第一个真正的应用程序,但它实际上是一个更通用的模式.其他常见用途包括错误处理和状态管理.语法糖(在Haskell中,F#中的计算表达式,C#中的Linq语法)仅仅是Monads的基础.
推荐阅读
路人甲
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有