Monads(二)
在上一篇文章中,我以OOP程序员而非函数式程序员的视角去审视了Monads,"Monad Pattern"是一种针对类型的设计模式,而"monad"则是应用这种模式的一个类型,让我们先来讲一些你肯定已经非常熟悉的monad类型,而非直接研究"Monad Pattern"本身
我在第一时间想到了以下类型,可能不是很完善,如果你觉得C#中还有其他常用的monad类型,请在评论区指出
Nullable<T>
—— 提供一个可能为null的T类型的值Func<T>
—— 提供一个可以"随机应变"的计算出来的T类型的值1Lazy<T>
—— 提供一个可以被指定的代码计算出来的T类型的值并将其缓存Task<T>
—— 提供一个可以被异步计算并在未来获取的T类型的值IEnumerable<T>
提供一个有序,只读的T类型列表,列表长度可以是0或者任意值
那么,这些类型有哪些共同之处呢?最明显的一处想必是他们都有且仅有一个泛型参数,并且处于一个非常尴尬的地位——除了Nullable<T>
以外,其余所有无论T是什么类型都可以正常运作,这些类型完全不知道其泛型参数具体的语义和实现,即便是Nullable<T>
,也仅仅"知道"T
必须是一个不可空的值类型2
注: 这个现象(
Nullable<T>
类型的限制)其实是历史原因所致,如果C#刚出生就伴随着泛型的话,那么Nullable<T>
就可以用任何类型去实现(不仅仅是不可空的值类型,而且C# 8的确做到了这一点)
另外一点就是这些类型都是"放大器(amplifiers)"(我非常感谢我的前同事在这篇文章(dc注: 已经没了)中对于monads的解释,它在很大程度上帮助我理解了这个概念)——某种可以增强其泛型参数的"表现力(原文representational power,此处翻译可能不准确)"的类型
好比一个byte
可以表示为其256个值中的任意一个,简单而又有用。我们也可以用泛型来很轻松的表述出诸如"一个可以被异步计算的可空字节序列(an asynchronously-computed sequence of nullable bytes)": Task<IEnumerable<Nullable<byte>>>
,这样就在不改变byte
本身性质的前提下大大了扩展他的能力
所以,难道monad指的就是能够给自己的泛型参数"扩展能力"的这些泛型类吗?也不尽然,为了实现"Monad Pattern",我们还需要了解更多内容,在下一节中,我们会操作这五种类型以试图找出他们其他的共性
-
dc注: 原文"represents a T that can be computed on demand",根据C#语言的实际特性,这里的意思应该是指
Func<T>
中T
的具体值在你调用这个委托的那一刻才会被确定,比如Func<int> rnd = () => new Random().NextInt()
,在这个例子中rnd
就是"随机应变"的,只有在你调用它的那一刻rnd里面包装的值才会被确定 ↩︎ -
dc注: 此处指
Nullable<T>
的泛型约束(generic constraint),Nullable<T>
在C#中的定义如下:public struct Nullable<T> where T : struct
。其中将泛型T
约束为struct
意味其必须是不可空的值类型(non-nullable value types),注意此处的"non-nullable"修饰"value types",也就是T必须是值类型,而且不能是另一个Nullable<T>
(值类型本身不可空) ↩︎
Comments | NOTHING