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

Help a C# developer understand: What is a monad?

最近有很多关于单子的讨论。我读过几篇文章/博客文章,但我无法用它们的例子来充分理解这个概念。原因是monads是一个功能语言概念,因此这些例子是用我没有使用过的语言(因为我没有深入使用功能语言)。我对句法的掌握不够深入,无法完全理解文章的内容…但我可以看出这里有一些值得理解的东西。

但是,我非常了解C,包括lambda表达式和其他功能特性。我知道C只有功能特性的一个子集,所以也许Monad不能用C表示。

然而,确实有可能传达这个概念吗?至少我希望如此。也许你可以提出一个C示例作为基础,然后描述C开发人员希望从那里做些什么,但是不能,因为语言缺乏功能性编程特性。这将是非常棒的,因为它将传达Monads的意图和好处。所以我的问题是:你能给C 3开发者最好的解释是什么?

谢谢!

(编辑:顺便说一句,我知道已经有至少3个"什么是单子"的问题了。但是,我也面临同样的问题…因此,这个问题是IMO所需要的,因为C-开发人员的重点。谢谢。


整天编程的大部分工作是将一些函数组合在一起,从中构建更大的函数。通常,您不仅在工具箱中有函数,而且还有其他一些东西,比如运算符、变量赋值等,但是通常,您的程序将许多"计算"组合在一起,以进行更大的计算,这些计算将进一步组合在一起。

Monad是一种"结合计算"的方法。

通常,将两个计算组合在一起的最基本的"运算符"是;

1
a; b

当你这样说的时候,你的意思是"先做a,然后做b"。结果a; b基本上是一个计算,可以与更多的东西结合在一起。这是一个简单的monad,它是一种将小计算与大计算相结合的方法。江户记1〔0〕说:"做左边的事,然后做右边的事"。

在面向对象语言中,另一个可以被视为monad的东西是.。你经常会发现这样的事情:

1
a.b().c().d()

.的基本意思是"对左边的计算进行评估,然后对结果调用右边的方法"。它是将函数/计算组合在一起的另一种方法,比;稍微复杂一些。将事物与.链接在一起的概念是单元的,因为它是将两个计算结合到一个新的计算中的一种方法。

另一个相当常见的monad没有特殊的语法,它是这样的模式:

2

返回值-1表示失败,但没有真正的方法来抽象出这种错误检查,即使您有许多API调用需要以这种方式组合。这基本上只是另一个monad,它按照规则"如果左边的函数返回-1,自己返回-1,否则调用右边的函数"组合函数调用。如果我们有一个操作员>>=做了这件事,我们可以简单地写下:

1
socket.bind(...) >>= socket.connect(...) >>= socket.send(...)

它将使事物更具可读性,并有助于抽象出我们组合函数的特殊方式,这样我们就不需要一次又一次地重复自己。

还有很多方法可以组合函数/计算,这些函数/计算作为一个通用模式很有用,并且可以在monad中进行抽象,使monad的用户能够编写更简洁和清晰的代码,因为所有使用的函数的簿记和管理都是在monad中完成的。

例如,上面的>>=可以扩展为"先进行错误检查,然后在作为输入的套接字上调用右边",这样我们就不需要多次明确指定socket

1
new socket() >>= bind(...) >>= connect(...) >>= send(...);

形式定义要复杂一些,因为您必须担心如何获得一个函数的结果作为下一个函数的输入,如果该函数需要该输入,并且因为您希望确保组合的函数符合您尝试在Monad中组合它们的方式。但基本的概念是,将不同的功能组合在一起的方式形式化。


我发表这个问题已经一年了。张贴后,我深入研究了哈斯克尔几个月。我非常喜欢它,但我把它放在一边,就像我准备钻研单子一样。我回到工作中,专注于我的项目所需的技术。

昨晚,我来重新阅读这些回复。最重要的是,我重新阅读了上面提到的布莱恩·贝克曼视频的文本评论中的具体C示例。它是如此的清晰和启发,我决定直接张贴在这里。

因为这个评论,我不仅觉得我完全理解Monads是什么……我意识到我实际上用C写了一些Monads……或者至少写得很近,并且努力解决同样的问题。

所以,这里是评论——这都是Sylvan在这里评论的直接引述:

0


monad本质上是延迟处理。如果你试图用一种不允许有副作用的语言编写代码(例如I/O),并且只允许纯计算,那么一个回避就是说,"好吧,我知道你不会为我做副作用,但是你能计算出如果你这样做会发生什么吗?"

有点作弊。

现在,这个解释将帮助你理解蒙纳兹的大局意图,但魔鬼在细节中。你到底是如何计算后果的?有时候,它不漂亮。

对于那些习惯于命令式编程的人来说,最好的方法是说它把你放在一个DSL中,在这个DSL中,语法上类似于你在monad之外所使用的操作被用来构建一个函数,如果你能(例如)写一个输出文件,它将做你想做的事情。几乎(但不是真的),就好像你在一个字符串中构建代码,以便以后被eval'd。


我相信其他用户会深入发布,但我发现这段视频在某种程度上有帮助,但我会说,我对这个概念仍然不够流利,这样我就可以(或应该)开始用monads直观地解决问题。


看我对"什么是单子?"

它以一个激励性的例子开始,通过这个例子工作,派生出一个monad的例子,并正式定义"monad"。

它假定不了解函数编程,并且使用具有最简单表达式的function(argument) := expression语法的伪代码。

这个C程序是伪代码monad的一个实现。(参考:M为类型构造函数,feed为"绑定"操作,wrap为"返回"操作。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
using System.IO;
using System;

class Program
{
    public class M<A>
    {
        public A val;
        public string messages;
    }

    public static M feed<A, B>(Func<A, M> f, M<A> x)
    {
        M m = f(x.val);
        m.messages = x.messages + m.messages;
        return m;
    }

    public static M<A> wrap<A>(A x)
    {
        M<A> m = new M<A>();
        m.val = x;
        m.messages ="";
        return m;
    }

    public class T {};
    public class U {};
    public class V {};

    public static M<U> g(V x)
    {
        M<U> m = new M<U>();
        m.messages ="called g.
"
;
        return m;
    }

    public static M<T> f(U x)
    {
        M<T> m = new M<T>();
        m.messages ="called f.
"
;
        return m;
    }

    static void Main()
    {
        V x = new V();
        M<T> m = feed<U, T>(f, feed(g, wrap<V>(x)));
        Console.Write(m.messages);
    }
}

您可以将monad看作类必须实现的C interface。这是一个实用主义的答案,它忽略了所有类别理论数学背后的原因,为什么你想选择在你的接口中有这些声明,忽略了所有的原因,为什么你想在一种语言中有monad,试图避免副作用,但我发现这是一个很好的开始,作为一个理解(c)接口的人。