在C#中,什么是Monad?

19 浏览
0 Comments

在C#中,什么是Monad?

现在关于monads的讨论很多。我读了一些文章/博客帖子,但是它们的例子没有让我完全掌握这个概念。原因是monads是一种函数式语言的概念,所以这些例子是用我没有深入使用过的语言编写的(因为我没有深入使用函数式语言)。我无法深入理解语法以完全理解文章...但是我可以感觉到其中有一些值得理解的东西。\n然而,我对C#非常了解,包括lambda表达式和其他函数式特性。我知道C#只有函数式特性的一个子集,所以也许无法在C#中表达monads。\n然而,肯定可以传达这个概念吧?至少我希望如此。也许你可以提供一个C#的例子作为基础,然后描述一个C#开发者希望从中做到但是由于语言缺乏函数式编程特性而无法实现的事情。这将非常棒,因为它能传达monads的意图和好处。所以,这是我的问题:你能给一个C# 3开发者关于monads的最好解释是什么?\n谢谢!(编辑:顺便说一下,我知道SO上已经有至少三个“什么是monad”的问题。然而,我遇到了同样的问题...所以我认为这个问题是必要的,因为它关注C#开发者。谢谢。)

0
0 Comments

在C#中,什么是Monad?

Monad本质上是延迟处理。如果你想在一种不允许副作用(例如I/O)的语言中编写有副作用的代码,并且只允许纯计算,一个巧妙的方法是说:“好吧,我知道你不会为我执行副作用,但你能计算一下如果执行了副作用会发生什么吗?”

这有点像作弊。

现在,这个解释将帮助你理解Monad的大致意图,但魔鬼在于细节。你到底如何计算后果?有时候,这并不美观。

对于习惯于命令式编程的人来说,最好的方法是概述一下:它将你置于一种DSL中,在这个DSL中,外部Monad之外的操作看起来在语法上与你习惯的操作相似,但实际上是用来构建一个函数的,如果你可以(例如)写入一个输出文件,它将执行你想要的操作。几乎(但实际上并不是)就像是在字符串中构建代码以便稍后进行eval。

就像《我,机器人》这本书中的情节?科学家要求计算机计算航天旅行,并要求跳过某些规则?:):):)

嗯,Monad可以用于延迟处理和封装具有副作用的函数,事实上,这是它在Haskell中的第一个真正应用,但它实际上是一种更通用的模式。其他常见的用途包括错误处理和状态管理。语法糖(Haskell中的do表达式,F#中的Computation Expressions,C#中的Linq语法)只是Monad本身的一部分。

Monad用于错误处理(Maybe和Either e的实例)和状态管理(State s,ST s)的实例让我感到特别有意思,它们就像是“请计算一下如果你为我执行了[副作用]会发生什么”的特殊实例。另一个例子是非确定性([])。

这是完全正确的;但有一个(嗯,两个)补充,它是一个E DSL,即嵌入式DSL,因为每个“monadic”值都是你的“纯”语言本身的有效值,代表一个潜在的不纯的“计算”。此外,你的纯语言中存在一个monadic的“bind”结构,它允许你链接这些值的纯构造器,每个构造器将使用其前一次计算的结果进行调用,当整个组合计算被“运行”时。这意味着我们有能力对未来的结果进行分支(或者在任何情况下,对分开的“运行”时间线进行分支)。

但对于程序员来说,这意味着我们可以在EDSL中进行编程,同时与纯语言的纯计算混合。一个多层堆叠的三明治是一个多层堆叠的三明治。就是这么简单。

0
0 Comments

一个问题的出现是因为在这个帖子发布了一年之后,作者重新阅读了之前的回复以及相关的C#示例。作者在阅读了这些内容之后,对monad有了更深刻的理解,并且意识到自己在C#中实际上已经编写了一些monad,或者至少非常接近,并且正在解决相同的问题。

在这个问题中,作者提到了一个评论,评论中给出了一个C#示例,非常清晰和明确地解释了monad。这个示例是一个使用Nullable类型的maybe monad的例子,用于处理可能返回null的值的情况。示例代码展示了如何使用bind操作符将多个nullable值组合起来,并在任何一个为null时返回null。作者通过这个示例理解了monad的本质,并且意识到自己在C#中已经编写了类似的代码。

在示例代码中,作者定义了一个静态函数Bind,它接受一个Nullable类型的值和一个从该值到Nullable类型的函数,并根据值是否为null来执行相应的操作。如果值不为null,则将其传递给函数并返回结果;如果值为null,则直接返回null。这样,使用monad的代码可以完全摆脱对null的检查,只需要使用Bind操作符将值绑定到变量上,然后可以像正常的int类型一样使用它们。

还有monad的其他用途,例如在输入流中使用monad来编写解析器组合器。通过使用monad,可以将各个解析器组合在一起,而不必关心回溯、解析失败等问题。这样,使用monad的代码可以像处理正常的输入一样,而不必关心底层的复杂逻辑。

最后,还有在Haskell中实现相同代码的示例。Haskell使用do语法糖来使代码看起来像命令式代码一样,这是通过实现bind操作符和return函数来实现的。通过定义这两个函数,可以自定义自己的monad,并且可以像编写自己的小语言一样使用do语法糖。

这个问题的出现是因为作者重新阅读了之前的回复和示例代码,并对monad有了更深刻的理解。通过这个问题和相关的讨论,读者可以更好地理解monad的概念和用途。

0
0 Comments

C#中的monad是什么?

在编程中,你每天所做的大部分工作都是将一些函数组合在一起,从而构建更大的函数。通常,你的工具箱中不仅包含函数,还包括运算符、变量赋值等其他内容,但通常你的程序将许多“计算”组合成更大的计算,这些计算将进一步组合在一起。

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上调用右侧的内容”,这样我们就不需要明确指定socket很多次了:

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

正式的定义稍微复杂一些,因为你必须考虑如何将一个函数的结果作为下一个函数的输入,如果该函数需要该输入,并且你希望确保你组合的函数适合你尝试在monad中组合它们的方式。但基本概念就是你形式化了不同的将函数组合在一起的方式。

很棒的答案!我要引用Oliver Steele的一句话,试图将monad与C++或C#中的运算符重载联系起来:monad允许你重载“;”运算符。

你确定你知道monad是什么吗?monad不是一个“函数”或计算,它有一些规则。

在你的;的例子中:它将;映射到哪些对象/数据类型?(想一下ListT映射到List<T>)它如何映射对象/数据类型之间的态射/函数?;purejoinbind是什么?

;将语句映射到按顺序应用thunk的操作。

0