在 C# 中,什么是 monad?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/674855/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 12:57:05  来源:igfitidea点击:

In C#, What is a monad?

c#c#-3.0lambdamonads

提问by Charlie Flowers

There is a lot of talk about monads these days. I have read a few articles / blog posts, but I can't go far enough with their examples to fully grasp the concept. The reason is that monads are a functional language concept, and thus the examples are in languages I haven't worked with (since I haven't used a functional language in depth). I can't grasp the syntax deeply enough to follow the articles fully ... but I can tell there's something worth understanding there.

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

However, I know C# pretty well, including lambda expressions and other functional features. I know C# only has a subset of functional features, and so maybe monads can't be expressed in C#.

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

However, surely it is possible to convey the concept? At least I hope so. Maybe you can present a C# example as a foundation, and then describe what a C# developer would wishhe could do from there but can't because the language lacks functional programming features. This would be fantastic, because it would convey the intent and benefits of monads. So here's my question: What is the best explanation you can give of monads to a C# 3 developer?

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

Thanks!

谢谢!

(EDIT: By the way, I know there are at least 3 "what is a monad" questions already on SO. However, I face the same problem with them ... so this question is needed imo, because of the C#-developer focus. Thanks.)

(编辑:顺便说一句,我知道至少有 3 个“什么是 monad”问题已经在 SO 上。但是,我面临着同样的问题......所以这个问题是需要的,因为 C#-developer重点。谢谢。)

回答by MarkusQ

A monad is essentially deferred processing. If you are trying to write code that has side effects (e.g. I/O) in a language that does not permit them, and only allows pure computation, one dodge is to say, "Ok, I know you won't do side effects for me, but can you please compute what would happen if you did?"

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

It's sort of cheating.

有点作弊。

Now, that explanation will help you understand the big picture intent of monads, but the devil is in the details. How exactly doyou compute the consequences? Sometimes, it isn't pretty.

现在,该解释将帮助您了解 monad 的大局意图,但问题在于细节。你是如何计算后果的?有时,它并不漂亮。

The best way to give an overview of the how for someone used to imperative programming is to say that it puts you in a DSL wherein operations that look syntactically like what you are used to outside the monad are used instead to build a function that would do what you want if you could (for example) write to an output file. Almost (but not really) as if you were building code in a string to later be eval'd.

对习惯命令式编程的人如何进行概述的最好方法是说它把你放在一个 DSL 中,其中在语法上看起来像你在 monad 之外的操作的操作被用来构建一个可以做的函数如果您可以(例如)写入输出文件,您想要什么。几乎(但不是真的)就像您在字符串中构建代码以供稍后评估一样。

回答by TheMissingLINQ

I'm sure other users will post in-depth, but I found this videohelpful to an extent, but I will say that I'm still not to the point of fluency with the concept such that I could (or should) begin solving problems intuitively with Monads.

我相信其他用户会深入发帖,但我发现这个视频在某种程度上很有帮助,但我会说我还没有达到我可以(或应该)开始解决这个概念的程度使用 Monad 直观地解决问题。

回答by hao

You can think of a monad as a C# interfacethat classes have to implement. This is a pragmatic answer that ignores all the category theoretical math behind why you'd want to choose to have these declarations in your interface and ignores all the reasons why you'd want to have monads in a language that tries to avoid side effects, but I found it to be a good start as someone who understands (C#) interfaces.

您可以将 monad 视为类必须实现的 C#interface。这是一个务实的答案,它忽略了为什么要选择在界面中包含这些声明的所有类别理论数学,并忽略了为什么要在试图避免副作用的语言中使用 monad 的所有原因,但我发现这是一个了解 (C#) 接口的人的良好开端。

回答by sth

Most of what you do in programming all day is combining some functions together to build bigger functions from them. Usually you have not only functions in your toolbox but also other things like operators, variable assignments and the like, but generally your program combines together lots of "computations" to bigger computations that will be combined together further.

您在编程中所做的大部分工作都是将一些功能组合在一起以从它们构建更大的功能。通常,您的工具箱中不仅有函数,还有其他东西,如运算符、变量赋值等,但通常您的程序将大量“计算”组合在一起,以进行更大的计算,这些计算将进一步组合在一起。

A monad is some way to do this "combining of computations".

monad 是实现这种“计算组合”的某种方式。

Usually your most basic "operator" to combine two computations together is ;:

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

a; b

When you say this you mean "first do a, then do b". The result a; bis basically again a computation that can be combined together with more stuff. This is a simple monad, it is a way of combing small computations to bigger ones. The ;says "do the thing on the left, then do the thing on the right".

当你说这句话时,你的意思是“先做a,然后做b”。结果a; b基本上又是一个可以与更多东西组合在一起的计算。这是一个简单的 monad,它是一种将小计算与大计算相结合的方式。该;说“我们该做的左侧,再做右侧的事”。

Another thing that can be seen as a monad in object oriented languages is the .. Often you find things like this:

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

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

The .basically means "evaluate the computation on the left, and then call the method on the right on the result of that". It is another way to combine functions/computations together, a little more complicated than ;. And the concept of chaining things together with .is a monad, since it's a way of combining two computations together to a new computation.

.基本意思是“对左侧的计算,然后调用上的这个结果正确的方法”。这是将函数/计算组合在一起的另一种方式,比;. 将事物链接在一起的概念.是一个 monad,因为它是一种将两个计算组合到一个新计算的方式。

Another fairly common monad, that has no special syntax, is this pattern:

另一个没有特殊语法的相当常见的 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;

A return value of -1 indicates failure, but there is no real way to abstract out this error checking, even if you have lots of API-calls that you need to combine in this fashion. This is basically just another monad that combines the function calls by the rule "if the function on the left returned -1, do return -1 ourselves, otherwise call the function on the right". If we had an operator >>=that did this thing we could simply write:

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

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

It would make things more readable and help to abstract out our special way of combining functions, so that we don't need to repeat ourselves over and over again.

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

And there are many more ways to combine functions/computations that are useful as a general pattern and can be abstracted in a monad, enabling the user of the monad to write much more concise and clear code, since all the book-keeping and management of the used functions is done in the monad.

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

For example the above >>=could be extended to "do the error checking and then call the right side on the socket that we got as input", so that we don't need to explicitly specify socketlots of times:

例如,上面的内容>>=可以扩展为“进行错误检查,然后调用我们作为输入获得的套接字的右侧”,这样我们就不需要socket多次明确指定:

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

The formal definition is a bit more complicated since you have to worry about how to get the result of one function as an input to the next one, if that function needs that input and since you want to make sure that the functions you combine fit into the way you try to combine them in your monad. But the basic concept is just that you formalize different ways to combine functions together.

正式定义有点复杂,因为您必须担心如何将一个函数的结果作为下一个函数的输入,如果该函数需要该输入,并且您想确保组合的函数适合您尝试将它们组合到您的 monad 中的方式。但基本概念只是将不同的方法组合在一起。

回答by Charlie Flowers

It has been a year since I posted this question. After posting it, I delved into Haskell for a couple of months. I enjoyed it tremendously, but I placed it aside just as I was ready to delve into Monads. I went back to work and focused on the technologies my project required.

我发布这个问题已经一年了。发布后,我深入研究了 Haskell 几个月。我非常喜欢它,但是当我准备深入研究 Monad 时,我把它放在一边。我回去工作并专注于我的项目所需的技术。

And last night, I came and re-read these responses. Most importantly, I re-read the specific C# examplein the text comments of the Brian Beckman videosomeone mentions above. It was so completely clear and illuminating that I've decided to post it directly here.

昨晚,我来重新阅读这些回复。最重要的是,我重新阅读上面有人提到的 Brian Beckman 视频的文本注释中特定 C# 示例。它非常清晰和有启发性,我决定直接在这里发布。

Because of this comment, not only do I feel like I understand exactlywhat Monads are … I realize I've actually written some things in C# that areMonads … or at least very close, and striving to solve the same problems.

正因为如此评论,而不是只做我觉得我明白究竟是什么单子......我意识到我其实C#编写的一些事情,单子......或者至少非常接近,努力解决同样的问题。

So, here's the comment – this is all a direct quote from the comment hereby sylvan:

所以,这里的评论-这是所有直接引用此评论西尔万

This is pretty cool. It's a bit abstract though. I can imagine people who don't know what monads are already get confused due to the lack of real examples.

So let me try to comply, and just to be really clear I'll do an example in C#, even though it will look ugly. I'll add the equivalent Haskell at the end and show you the cool Haskell syntactic sugar which is where, IMO, monads really start getting useful.

Okay, so one of the easiest Monads is called the "Maybe monad" in Haskell. In C# the Maybe type is called Nullable<T>. It's basically a tiny class that just encapsulates the concept of a value that is either valid and has a value, or is "null" and has no value.

A useful thing to stick inside a monad for combining values of this type is the notion of failure. I.e. we want to be able to look at multiple nullable values and return nullas soon as any one of them is null. This could be useful if you, for example, look up lots of keys in a dictionary or something, and at the end you want to process all of the results and combine them somehow, but if any of the keys are not in the dictionary, you want to return nullfor the whole thing. It would be tedious to manually have to check each lookup for nulland return, so we can hide this checking inside the bind operator (which is sort of the point of monads, we hide book-keeping in the bind operator which makes the code easier to use since we can forget about the details).

Here's the program that motivates the whole thing (I'll define the Bindlater, this is just to show you why it's nice).

 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();
        }
    }

Now, ignore for a moment that there already is support for doing this for Nullablein C# (you can add nullable ints together and you get null if either is null). Let's pretend that there is no such feature, and it's just a user-defined class with no special magic. The point is that we can use the Bindfunction to bind a variable to the contents of our Nullablevalue and then pretend that there's nothing strange going on, and use them like normal ints and just add them together. We wrap the result in a nullable at the end, and that nullable will either be null (if any of f, gor hreturns null) or it will be the result of summing f, g, and htogether. (this is analogous of how we can bind a row in a database to a variable in LINQ, and do stuff with it, safe in the knowledge that the Bindoperator will make sure that the variable will only ever be passed valid row values).

You can play with this and change any of f, g, and hto return null and you will see that the whole thing will return null.

So clearly the bind operator has to do this checking for us, and bail out returning null if it encounters a null value, and otherwise pass along the value inside the Nullablestructure into the lambda.

Here's the Bindoperator:

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;
}

The types here are just like in the video. It takes an M a(Nullable<A>in C# syntax for this case), and a function from ato M b(Func<A, Nullable<B>>in C# syntax), and it returns an M b(Nullable<B>).

The code simply checks if the nullable contains a value and if so extracts it and passes it onto the function, else it just returns null. This means that the Bindoperator will handle all the null-checking logic for us. If and only if the value that we call Bindon is non-null then that value will be "passed along" to the lambda function, else we bail out early and the whole expression is null. This allows the code that we write using the monad to be entirely free of this null-checking behaviour, we just use Bindand get a variable bound to the value inside the monadic value (fval, gvaland hvalin the example code) and we can use them safe in the knowledge that Bindwill take care of checking them for null before passing them along.

There are other examples of things you can do with a monad. For example you can make the Bindoperator take care of an input stream of characters, and use it to write parser combinators. Each parser combinator can then be completely oblivious to things like back-tracking, parser failures etc., and just combine smaller parsers together as if things would never go wrong, safe in the knowledge that a clever implementation of Bindsorts out all the logic behind the difficult bits. Then later on maybe someone adds logging to the monad, but the code using the monad doesn't change, because all the magic happens in the definition of the Bindoperator, the rest of the code is unchanged.

Finally, here's the implementation of the same code in Haskell (--begins a comment line).

-- 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)

As you can see the nice donotation at the end makes it look like straight imperative code. And indeed this is by design. Monads can be used to encapsulate all the useful stuff in imperative programming (mutable state, IO etc.) and used using this nice imperative-like syntax, but behind the curtains, it's all just monads and a clever implementation of the bind operator! The cool thing is that you can implement your own monads by implementing >>=and return. And if you do so those monads will also be able to use the donotation, which means you can basically write your own little languages by just defining two functions!

这很酷。不过有点抽象。我可以想象那些不知道什么是 monad 的人已经因为缺乏真实的例子而感到困惑。

所以让我试着遵守,为了清楚起见,我会用 C# 做一个例子,即使它看起来很难看。我将在最后添加等效的 Haskell 并向您展示很酷的 Haskell 语法糖,IMO 在这里,monads 真正开始变得有用。

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

将这种类型的值放在 monad 中的一个有用的东西是失败的概念。即我们希望能够查看多个可null为空的值并在其中任何一个为空时立即返回。例如,如果您在字典或其他东西中查找大量键,并且最后您想处理所有结果并以某种方式组合它们,这可能很有用,但是如果任何键不在字典中,你想返回null整个事情。手动检查每个查找null和返回是很乏味的 ,所以我们可以将这个检查隐藏在绑定操作符中(这有点像 monad,我们在绑定操作符中隐藏了簿记,这使得代码更容易使用,因为我们可以忘记细节)。

这是激发整个事情的程序(我将在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();
        }
    }

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

你可以用这个玩和改变任何fgh返回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 a(Nullable<A>在 C# 语法中) 和一个函数 from ato M b(Func<A, Nullable<B>>在 C# 语法中),它返回一个M b( Nullable<B>)。

代码只是检查可空对象是否包含一个值,如果是,则提取它并将其传递给函数,否则它只返回 null。这意味着Bind操作符将为我们处理所有的空检查逻辑。当且仅当我们调用的值 Bind不为空时,该值才会“传递”给 lambda 函数,否则我们提前退出,整个表达式为空。这允许我们编写使用的单子是完全免费的这个空检查行为,我们只是使用的代码Bind,并获得绑定到一元价值内值的变量(fvalgvalhval在示例代码),我们可以使用他们的安全在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 和绑定运算符的巧妙实现!很酷的一点是,您可以通过实现>>=和来实现自己的 monad return。如果你这样做,那些 monad 也将能够使用do符号,这意味着你基本上可以通过定义两个函数来编写自己的小语言!

回答by Jordan

See my answerto "What is a monad?"

请参阅我对“什么是 monad?”的回答

It begins with a motivating example, works through the example, derives an example of a monad, and formally defines "monad".

它从一个激励性的例子开始,通过这个例子,推导出一个 monad 的例子,并正式定义了“monad”。

It assumes no knowledge of functional programming and it uses pseudocode with function(argument) := expressionsyntax with the simplest possible expressions.

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

This C# program is an implementation of the pseudocode monad. (For reference: Mis the type constructor, feedis the "bind" operation, and wrapis the "return" operation.)

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

using System.IO;
using System;

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

    public static M<B> feed<A, B>(Func<A, M<B>> f, M<A> x)
    {
        M<B> 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.\n";
        return m;
    }

    public static M<T> f(U x)
    {
        M<T> m = new M<T>();
        m.messages = "called f.\n";
        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);
    }
}