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

这些天有很多关于monad的讨论。 我已经阅读了一些文章/博客文章,但我无法用他们的例子去充分掌握这个概念。 原因是monad是一个功能性语言概念,因此这些例子都是我没有使用过的语言(因为我没有深入地使用函数式语言)。 我无法深入地理解语法以充分跟踪文章......但我可以告诉那里有值得理解的东西。

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

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

谢谢!

(编辑:顺便说一句,我知道至少有3个“单子”是什么,但是,我面临着同样的问题...所以这个问题需要imo,因为C#开发人员重点,谢谢。)


你整天编程的大部分功能是将一些功能组合在一起,从而构建更大的功能。 通常,你不仅在你的工具箱中有函数,而且还有其他一些东西,比如运算符,变量赋值等,但是通常你的程序将大量的“计算”结合到更大的计算中,这些计算将进一步结合在一起。

monad是一种“结合计算”的方法。

通常你最基本的“操作员”将两个计算结合在一起;

a; b

当你这样说时,你的意思是“先做a ,然后做b ”。 结果a; b a; b基本上又是一个可以与更多东西结合在一起的计算。 这是一个简单的monad,它是将较小的计算结合到较大的计算的一种方法。 The ; 说:“做左边的事情,然后做右边的事情”。

另一件可以在面向对象的语言中看作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。 我非常喜欢它,但是当我准备好钻研蒙纳德时,我把它放在一边。 我回去工作,专注于我的项目所需的技术。

昨天晚上,我来重读这些回复。 最重要的是,我重新阅读了上面提到的Brian Beckman视频的文本评论中的具体C#示例。 它非常清晰明了,我决定直接在这里发布。

由于这个评论,我不仅觉得我完全明白Monads是什么......我意识到我已经用C#写了一些Monad的东西......或者至少非常接近,并且努力解决相同的问题。

所以,这里是评论 - 这是所有来自sylvan评论的直接引用:

这很酷。 这虽然有点抽象。 我可以想象,由于缺乏真实的例子,不知道单子的人已经感到困惑。

所以让我试着遵守,而且要清楚地说明,我将在C#中做一个例子,即使它看起来很丑。 我会在最后添加相应的Haskell,并向您展示酷酷的Haskell语法糖,这是IMO,monads真正开始变得有用的地方。

好吧,所以最简单的Monad之一在Haskell被称为“Maybe monad”。 在C#中,Maybe类型被称为Nullable<T> 。 它基本上是一个微小的类,它只是封装了一个有效并且有一个值的概念,或者是“null”并且没有任何价值。

坚持在单子中结合这种类型的价值的一个有用的东西是失败的概念。 也就是说,我们希望能够查看多个可为null的值,并在其中的任何一个为null立即返回null。 例如,如果你在字典中查找很多键,并且最后想要处理所有结果并以某种方式将它们组合起来,那么这会很有用,但是如果任何键不在字典中,你想为整个事情返回null 。 手动检查每个查找是否为null并返回是非常乏味的,因此我们可以在bind操作符中隐藏这种检查(这就是monads的要点,我们隐藏了绑定操作符中的簿记,这使代码更容易使用,因为我们可以忘记细节)。

这是激励整个事情的程序(我将在后面定义Bind ,这只是为了告诉你为什么它很好)。

 class Program
    {
        static Nullable<int> f(){ return 4; }        
        static Nullable<int> g(){ return 7; }
        static Nullable<int> h(){ return 9; }


        static void Main(string[] args)
        {
            Nullable<int> z = 
                        f().Bind( fval => 
                            g().Bind( gval => 
                                h().Bind( hval =>
                                    new Nullable<int>( fval + gval + hval ))));

            Console.WriteLine(
                    "z = {0}", z.HasValue ? z.Value.ToString() : "null" );
            Console.WriteLine("Press any key to continue...");
            Console.ReadKey();
        }
    }

现在,暂时忽略已经支持在C#中为Nullable执行此操作(您可以一起添加可为null的int,如果其中任何一个为null,您将获得null)。 让我们假装没有这样的功能,它只是一个用户定义的类,没有特殊的魔法。 关键是我们可以使用Bind函数将变量Bind到我们的Nullable值的内容,然后假装没有什么奇怪的事情发生,并像普通整数一样使用它们,并将它们加在一起。 我们在最后将结果包装为可空,并且可以为空(如果fgh任何一个返回null),或者它将是将fgh一起求和的结果。 (这与我们如何将数据库中的一行绑定到LINQ中的变量类似,并且知道Bind运算符将确保该变量只能传递有效的行值)。

你可以玩这个,改变fgh任何一个以返回null,你会看到整个事物将返回null。

很显然,绑定操作符必须为我们执行此操作,并且如果遇到null值,则返回null,否则将Nullable结构内的值传递给lambda。

这是Bind操作符:

public static Nullable<B> Bind<A,B>( this Nullable<A> a, Func<A,Nullable<B>> f ) 
    where B : struct 
    where A : struct
{
    return a.HasValue ? f(a.Value) : null;
}

这里的类型就像在视频中一样。 它需要一个M aNullable<A>从这种情况下,C#语法),和一个功能aM bFunc<A, Nullable<B>>在C#语法),并返回一个M bNullable<B> )。

代码简单地检查nullable是否包含一个值,如果是,则将其提取并传递给函数,否则它只返回null。 这意味着Bind操作符将为我们处理所有的空值检查逻辑。 当且仅当我们调用Bind on的值非空时,那个值将被“传递”给lambda函数,否则我们提前退出并且整个表达式为空。 这允许我们使用monad编写的代码完全没有这种空值检查行为,我们只是使用Bind并获得绑定到fval值内的值的变量(示例代码中的fvalgvalhval ),而我们可以安全使用它们,因为Bind会在传递它们之前检查它们是否为null。

还有其他一些你可以用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和绑定操作符的巧妙实现! 最酷的是你可以通过实现>>=return来实现你自己的monads。 如果你这样做的单子还可以使用do符号,这意味着你基本上可以通过只定义两个函数编写自己的小语言!


monad基本上是延迟处理。 如果你想用不允许它们的语言编写有副作用的代码(例如I / O),并且只允许纯计算,那么闪避就是说:“好吧,我知道你不会做副作用对我来说,但是请你计算一下如果你做了什么会发生什么?“

这有点作弊。

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

概述某人习惯于命令式编程的最佳方式是说,它将您置于DSL中,其中使用语法上看起来像您在单子之外使用的操作,而不是用于构建可以执行如果可以(例如)写入输出文件,则需要什么。 几乎(但不是真的),就好像你在一个字符串中构建代码,以后将被评估。

链接地址: http://www.djcxy.com/p/47673.html

上一篇: Help a C# developer understand: What is a monad?

下一篇: How to detect a Monad?