我发布这个问题已经一年了。发布后,我进入了Haskell了几个月。我非常喜欢它,但是当我准备深入研究Monads时,我把它放在一旁。我回去工作,专注于项目所需的技术。
这很酷。虽然有点抽象。我可以想象,由于缺乏真实的例子,不知道什么单子的人已经感到困惑。
因此,让我尝试遵守,并且要明确一点,尽管它看起来很丑,但我仍将使用C#进行示例。我将在末尾添加等效的Haskell,并向您展示凉爽的Haskell语法糖,这是IMO,单核函数真正开始变得有用的地方。
好的,最简单的Monad之一在Haskell中被称为“也许Monad”。在C#中,Maybe类型称为Nullable<T>
。基本上,这是一个很小的类,只封装了一个值的概念,该值要么有效且具有值,要么为“ null”且没有值。
插入单子以组合此类型的值时有用的事情是失败的概念。也就是说,我们希望能够查看多个可null
为空的值,并在其中任何一个为空时立即返回。例如,如果您在字典中查找大量关键字或其他内容,并且最后要处理所有结果并以某种方式组合它们,则这可能会很有用,但是如果任何关键字不在词典中,你想null
整个回来。手动检查每个查找
null
并返回很麻烦,因此我们可以将此检查隐藏在bind运算符内(这是单子的要点,我们将簿记隐藏在bind运算符中,这使代码更容易使用,因为我们可以忘记细节)。
这是激发整个过程的程序(我将在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#中已经为此提供了支持(您可以将可为null的int一起添加,如果其中任何一个为null,则为null)。让我们假装没有这种功能,它只是一个用户定义的类,没有特殊的魔术。关键是我们可以使用该Bind
函数将变量绑定到Nullable
值的内容,然后假装没有什么奇怪的事情,可以像正常int一样使用它们并将它们加在一起。我们总结的结果在最后一空,并为空的要么是空(如果有的话f
,g
或h
返回null)或将是总结的结果f
,g
和h
一起。(这类似于我们如何将数据库中的行绑定到LINQ中的变量,并对其进行填充,这是安全的,前提是Bind
操作员将确保仅向变量传递有效的行值)。
您可以使用它并更改,和中的任何一个f
,g
并h
返回null,您将看到整个事情都将返回null。
因此,很显然,bind运算符必须为我们执行此检查,并在遇到空值时避免返回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#语法)和一个函数a
to
M b
(Func<A, Nullable<B>>
在C#语法中),然后返回M b
(Nullable<B>
)。
该代码仅检查nullable是否包含值,如果是则将其提取并将其传递给函数,否则它仅返回null。这意味着Bind
操作员将为我们处理所有空检查逻辑。当且仅当我们调用的值
Bind
不为null时,该值才会“传递”给lambda函数,否则我们会尽早纾困,并且整个表达式为null。这允许我们编写使用的单子是完全免费的这个空检查行为,我们只是使用的代码Bind
,并获得绑定到一元价值内值的变量(fval
,
gval
并hval
在示例代码),我们可以使用他们的安全Bind
会在传递它们之前检查它们是否为null 的知识。
您还可以使用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
符号使它看起来像直接的命令性代码。实际上,这是设计使然。Monad可以用于封装命令式编程(可变状态,IO等)中的所有有用内容,并使用这种类似于命令式的漂亮语法使用,但是在幕后,它们全都是monads和bind操作符的巧妙实现!很酷的事情是,您可以通过实现>>=
和来实现自己的monad return
。如果这样做,这些单子也将能够使用该do
表示法,这意味着您只需定义两个函数就可以基本上编写自己的小语言!