UPDATE: This question was the subject of an immensely long blog series, which you can read at Monads — thanks for the great question!
(更新:这个问题是一个非常长的博客系列的主题,您可以在Monads上阅读它 -感谢您提出的伟大问题!)
In terms that an OOP programmer would understand (without any functional programming background), what is a monad?
(用OOP程序员会理解的术语(没有任何函数式编程背景),什么是monad?)
A monad is an "amplifier" of types that obeys certain rules and which has certain operations provided .
(monad是一种类型的“放大器”,它遵循某些规则并提供某些操作 。)
First, what is an "amplifier of types"?
(首先,什么是“类型放大器”?)
By that I mean some system which lets you take a type and turn it into a more special type. (我的意思是说,有一个系统可以让您选择一种类型并将其转换为更特殊的类型。)
For example, in C# consider Nullable<T>
. (例如,在C#中考虑Nullable<T>
。)
This is an amplifier of types. (这是一种放大器。)
It lets you take a type, say int
, and add a new capability to that type, namely, that now it can be null when it couldn't before. (它允许您采用一个类型,例如int
,并为该类型添加新功能,即现在可以在以前无法使用的情况下将其设置为null。)
As a second example, consider IEnumerable<T>
.
(作为第二个示例,请考虑IEnumerable<T>
。)
It is an amplifier of types. (它是一种类型的放大器。)
It lets you take a type, say, string
, and add a new capability to that type, namely, that you can now make a sequence of strings out of any number of single strings. (它允许您采用一种类型,例如string
,并为该类型添加新功能,即,您现在可以从任意数量的单个字符串中构成一个字符串序列。)
What are the "certain rules"?
(什么是“某些规则”?)
Briefly, that there is a sensible way for functions on the underlying type to work on the amplified type such that they follow the normal rules of functional composition. (简而言之,存在一种合理的方式,使基础类型上的功能在放大类型上起作用,从而使它们遵循功能组成的正常规则。)
For example, if you have a function on integers, say (例如,如果您有一个整数函数,请说)
int M(int x) { return x + N(x * 2); }
then the corresponding function on Nullable<int>
can make all the operators and calls in there work together "in the same way" that they did before.
(那么Nullable<int>
上的相应函数可以使所有运算符和其中的调用“像以前一样”一起工作。)
(That is incredibly vague and imprecise; you asked for an explanation that didn't assume anything about knowledge of functional composition.)
((这是非常模糊和不精确的;您要求提供一种解释,该解释没有假定任何有关功能组成的知识。))
What are the "operations"?
(什么是“操作”?)
There is a "unit" operation (confusingly sometimes called the "return" operation) that takes a value from a plain type and creates the equivalent monadic value.
(有一个“单位”操作(有时也称为“返回”操作),该操作从普通类型获取值并创建等效的单价值。)
This, in essence, provides a way to take a value of an unamplified type and turn it into a value of the amplified type. (本质上,这提供了一种获取未放大类型的值并将其转换为放大类型的值的方法。)
It could be implemented as a constructor in an OO language. (可以将其实现为OO语言的构造函数。)
There is a "bind" operation that takes a monadic value and a function that can transform the value, and returns a new monadic value.
(有一个“绑定”操作,它接受一个单子值和一个可以转换该值并返回新单子值的函数。)
Bind is the key operation that defines the semantics of the monad. (绑定是定义monad语义的关键操作。)
It lets us transform operations on the unamplified type into operations on the amplified type, that obeys the rules of functional composition mentioned before. (它使我们可以将未放大类型的操作转换为对放大类型的操作,这要遵循前面提到的功能组成规则。)
There is often a way to get the unamplified type back out of the amplified type.
(通常有一种方法可以使未放大类型从放大类型中退回。)
Strictly speaking this operation is not required to have a monad. (严格来说,此操作不需要具有monad。)
(Though it is necessary if you want to have a comonad . We won't consider those further in this article.) ((尽管如果您想拥有自己的名字是很有必要的。我们将不在本文中进一步讨论。))
Again, take Nullable<T>
as an example.
(同样,以Nullable<T>
为例。)
You can turn an int
into a Nullable<int>
with the constructor. (您可以使用构造函数将int
转换为Nullable<int>
。)
The C# compiler takes care of most nullable "lifting" for you, but if it didn't, the lifting transformation is straightforward: an operation, say, (C#编译器会为您处理大多数可为空的“提升”,但是如果没有,提升转换将非常简单:例如,)
int M(int x) { whatever }
is transformed into
(变成)
Nullable<int> M(Nullable<int> x)
{
if (x == null)
return null;
else
return new Nullable<int>(whatever);
}
And turning a Nullable<int>
back into an int
is done with the Value
property.
(将Nullable<int>
回int
是通过Value
属性完成的。)
It's the function transformation that is the key bit.
(函数转换是关键。)
Notice how the actual semantics of the nullable operation — that an operation on a null
propagates the null
— is captured in the transformation. (请注意如何可空操作的实际语义-即在一个操作null
传播的null
-在转型抓获。)
We can generalize this. (我们可以对此进行概括。)
Suppose you have a function from int
to int
, like our original M
.
(假设您有一个从int
到int
的函数,就像我们原来的M
。)
You can easily make that into a function that takes an int
and returns a Nullable<int>
because you can just run the result through the nullable constructor. (您可以轻松地使它成为一个接受int
并返回Nullable<int>
的函数,因为您可以通过可为null的构造函数运行结果。)
Now suppose you have this higher-order method: (现在假设您具有以下高阶方法:)
static Nullable<T> Bind<T>(Nullable<T> amplified, Func<T, Nullable<T>> func)
{
if (amplified == null)
return null;
else
return func(amplified.Value);
}
See what you can do with that?
(看到你能做什么?)
Any method that takes an int
and returns an int
, or takes an int
and returns a Nullable<int>
can now have the nullable semantics applied to it . (现在,任何采用int
并返回int
或采用int
并返回Nullable<int>
都可以对其应用可为null的语义 。)
Furthermore: suppose you have two methods
(此外:假设您有两种方法)
Nullable<int> X(int q) { ... }
Nullable<int> Y(int r) { ... }
and you want to compose them:
(而您想组成它们:)
Nullable<int> Z(int s) { return X(Y(s)); }
That is, Z
is the composition of X
and Y
.
(也就是说, Z
是X
和Y
。)
But you cannot do that because X
takes an int
, and Y
returns a Nullable<int>
. (但是您不能这样做,因为X
接受一个int
,而Y
返回一个Nullable<int>
。)
But since you have the "bind" operation, you can make this work: (但是,由于您具有“绑定”操作,因此可以进行以下工作:)
Nullable<int> Z(int s) { return Bind(Y(s), X); }
The bind operation on a monad is what makes composition of functions on amplified types work.
(对单声道的绑定操作使放大类型上的功能组合起作用。)
The "rules" I handwaved about above are that the monad preserves the rules of normal function composition; (我上面挥挥手的“规则”是单子保留正常功能组成的规则。)
that composing with identity functions results in the original function, that composition is associative, and so on. (与标识函数组成的结果将产生原始函数,该组成具有关联性,依此类推。)
In C#, "Bind" is called "SelectMany".
(在C#中,“绑定”称为“ SelectMany”。)
Take a look at how it works on the sequence monad. (看一下它如何在序列monad上工作。)
We need to have two things: turn a value into a sequence and bind operations on sequences. (我们需要做两件事:将一个值转换为一个序列,然后对序列进行绑定操作。)
As a bonus, we also have "turn a sequence back into a value". (作为奖励,我们还具有“将序列变回值”的功能。)
Those operations are: (这些操作是:)
static IEnumerable<T> MakeSequence<T>(T item)
{
yield return item;
}
// Extract a value
static T First<T>(IEnumerable<T> sequence)
{
// let's just take the first one
foreach(T item in sequence) return item;
throw new Exception("No first item");
}
// "Bind" is called "SelectMany"
static IEnumerable<T> SelectMany<T>(IEnumerable<T> seq, Func<T, IEnumerable<T>> func)
{
foreach(T item in seq)
foreach(T result in func(item))
yield return result;
}
The nullable monad rule was "to combine two functions that produce nullables together, check to see if the inner one results in null; if it does, produce null, if it does not, then call the outer one with the result".
(可为空的monad规则是“将产生可为空的两个函数组合在一起,检查内部函数是否为null;如果满足,则产生null,否则为null,然后用结果调用外部函数”。)
That's the desired semantics of nullable. (这是可为空的所需语义。)
The sequence monad rule is "to combine two functions that produce sequences together, apply the outer function to every element produced by the inner function, and then concatenate all the resulting sequences together".
(序列monad规则是“将产生序列的两个函数组合在一起,将外部函数应用于内部函数产生的每个元素,然后将所有结果序列连接在一起”。)
The fundamental semantics of the monads are captured in the Bind
/ SelectMany
methods; (Monad的基本语义在Bind
/ SelectMany
方法中捕获;)
this is the method that tells you what the monad really means . (这是告诉您monad真正含义的方法 。)
We can do even better.
(我们可以做得更好。)
Suppose you have a sequences of ints, and a method that takes ints and results in sequences of strings. (假设您有一个整数序列,以及一个采用整数并产生字符串序列的方法。)
We could generalize the binding operation to allow composition of functions tha