Monad用简单的英语?(对于没有FP背景的OOP程序员)

用OOP程序员会理解的术语(没有任何函数式编程背景),什么是monad?

它解决了什么问题,最常使用的地方是什么?

编辑:

为了阐明我正在寻找的理解类型,假设您正在将具有monad的FP应用程序转换为OOP应用程序。您将如何将monad的责任移植到OOP应用程序?

回答:

更新:这个问题是一个非常长的博客系列的主题,您可以在Monads上阅读它-感谢您提出的伟大问题!

用OOP程序员会理解的术语(没有任何函数式编程背景),什么是monad?

monad是一种类型的“放大器”,它遵循某些规则并提供某些操作。

首先,什么是“类型放大器”?我的意思是说,有一个系统可以让您选择一种类型并将其转换为更特殊的类型。例如,在C#中考虑Nullable。这是一种放大器。它使您可以接受一个类型,例如int,并为该类型添加新功能,即现在可以在以前无法使用时为null。

作为第二个示例,请考虑IEnumerable<T>。它是一种类型的放大器。它使您可以采用一个类型,例如,string并为该类型添加新功能,即,您现在可以从任意数量的单个字符串中构成一个字符串序列。

什么是“某些规则”?简而言之,存在一种合理的方式,使基础类型上的功能在放大类型上起作用,从而使它们遵循功能组成的正常规则。例如,如果您有一个整数函数,请说

int M(int x) { return x + N(x * 2); }

那么相应的on函数Nullable可以使其中的所有运算符和调用以“以前相同的方式”一起工作。

(这是非常模糊和不精确的;您要求提供的解释不包含任何有关功能组成知识的假设。)

什么是“操作”?

有一个“单位”操作(有时也称为“返回”操作),该操作从普通类型获取值并创建等效的单价值。本质上,这提供了一种获取未放大类型的值并将其转换为放大类型的值的方法。可以将其实现为OO语言的构造函数。

有一个“绑定”操作,它接受一个单子值和一个可以转换该值并返回新单子值的函数。绑定是定义monad语义的关键操作。它使我们可以将未放大类型的操作转换为对放大类型的操作,它遵循前面提到的功能组成规则。

通常有一种方法可以使未放大类型从放大类型中退回。严格来说,此操作不需要具有monad。(尽管如果您想拥有自己的名字是很有必要的。我们将不在本文中进一步讨论。)

再以Nullable<T>一个例子为例。您可以使用构造函数将int转换为Nullable<int>。C#编译器会为您处理大多数可为空的“提升”,但是如果没有,提升转换将很简单:例如,

int M(int x) { whatever }

变成

Nullable<int> M(Nullable<int> x) 

{

if (x == null)

return null;

else

return new Nullable<int>(whatever);

}

并通过该属性将其变Nullable<int>回原样。intValue

函数转换是关键。注意,在转换中如何捕获可为空操作的实际语义,即可null传播操作的传播null。我们可以对此进行概括。

假设您具有从int到的功能int,就像我们原来的功能一样M。您可以轻松地将其设置为接受anint并返回a的函数,Nullable因为您可以通过可为null的构造函数运行结果。现在假设您具有以下高阶方法:

static Nullable<T> Bind<T>(Nullable<T> amplified, Func<T, Nullable<T>> func)

{

if (amplified == null)

return null;

else

return func(amplified.Value);

}

看到你能做什么?现在,任何采用anint并返回anint或采用anint并返回a的Nullable<int>方法现在都可以应用可空语义。

此外:假设您有两种方法

Nullable<int> X(int q) { ... }

Nullable<int> Y(int r) { ... }

而您想组成它们:

Nullable<int> Z(int s) { return X(Y(s)); }

即和Z的组成。但是您不能这样做,因为需要一个,然后返回一个。但是,由于您具有“绑定”操作,因此可以进行以下工作:XYXintYNullable

Nullable<int> Z(int s) { return Bind(Y(s), X); }

对单声道的绑定操作使放大类型上的功能组合起作用。我上面挥挥手的“规则”是单子保留正常功能组成的规则。与标识函数组成的结果将产生原始函数,该组成具有关联性,依此类推。

在C#中,“绑定”称为“ SelectMany”。看一下它如何在序列monad上工作。我们需要做两件事:将一个值转换为一个序列,然后对序列进行绑定操作。作为奖励,我们还具有“将序列变回值”的功能。这些操作是:

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;

}

可为空的monad规则是“将产生可为空的两个函数组合在一起,检查内部函数是否为null;如果满足,则产生null,否则为null,然后用结果调用外部函数”。这就是可为空的所需语义。

序列monad规则是“将产生序列的两个函数组合在一起,将外部函数应用于内部函数产生的每个元素,然后将所有结果序列连接在一起”。Bind/SelectMany方法捕获了monad的基本语义;这是告诉您monad真正含义的方法。

我们可以做得更好。假设您有一个整数序列,以及一个采用整数并产生字符串序列的方法。我们可以对绑定操作进行一般化,以允许采用接受和返回不同放大类型的功能,只要其中一个的输入与另一个的输出匹配即可:

static IEnumerable<U> SelectMany<T,U>(IEnumerable<T> seq, Func<T, IEnumerable<U>> func)

{

foreach(T item in seq)

foreach(U result in func(item))

yield return result;

}

因此,现在我们可以说“将这组单个整数放大为整数序列。将该特定整数转换为一串字符串,放大为一系列字符串。现在将这两个操作放在一起:将这组整数放大为”的串联所有的字符串序列。” 单子让您撰写的扩增。

它解决了什么问题,最常使用的地方是什么?

这就好比问“单例模式能解决什么问题?”,但我会给它一个机会。

Monad通常用于解决以下问题:

  • 我需要为此类型创建新功能,并且仍然要组合此类型上的旧功能以使用新功能。

  • 我需要捕获一堆关于类型的操作,并将这些操作表示为可组合的对象,构建越来越大的合成,直到我正确地表示了一系列操作为止,然后我需要从事情中得到结果。

  • 我需要用一种讨厌副作用的语言来清晰地表示副作用操作

    C#在其设计中使用了monad。如前所述,可为空的模式与“也许单子”非常相似。LINQ完全由monad组成。该SelectMany方法是操作组合的语义工作。(Erik Meijer喜欢指出,每个LINQ函数实际上都可以由实现SelectMany;其他一切只是为了方便。)

为了阐明我正在寻找的理解类型,假设您正在将具有monad的FP应用程序转换为OOP应用程序。您将如何将monad的责任移植到OOP应用程序中?

大多数OOP语言没有足够丰富的类型系统来直接表示monad模式本身。您需要一个类型系统,该系统支持比通用类型更高类型的类型。所以我不会尝试这样做。相反,我将实现代表每个monad的泛型类型,并实现代表所需的三个操作的方法:将一个值转换为一个放大的值,(也许)将一个放大的值转换为一个值,以及将未放大的值转换为一个函数放大值的函数。

一个很好的起点是我们如何在C#中实现LINQ。研究SelectMany方法;这是了解序列monad如何在C#中工作的关键。这是一种非常简单的方法,但功能非常强大!

以上是 Monad用简单的英语?(对于没有FP背景的OOP程序员) 的全部内容, 来源链接: utcz.com/qa/400701.html

回到顶部