C# 查询表达式和 F# 计算表达式,可以使用函数,行为方式非标准(返回单子值),就好像返回普通值。我们在这一节使用的计算类型是 ValueWrapper <T>,因此,原始函数将返回值的类型是 ValueWrapper<T>,而不是 T。
实现这些函数,既可以使用其他的查询或计算表达式,也可以通过直接创建计算类型的值。有些计算表达式可以封装复杂的逻辑,所以,直接创建值可能有困难。这时,通常写一些返回这种计算类型的基本操作,再使用这些基本操作实现其他的一切。然而,构建 ValueWrapper <T> 类型的值并不困难。下面的代码演示了用 C#实现的一个方法,从控制台读一个数字,并把它打包到这个计算类型内:
ValueWrapper<int> ReadInt() {
int num =Int32.Parse(Console.ReadLine());
return newValueWrapper<int>(num);
}
这个方法从控制台读一个数字,并把它打包到 ValueWrapper <T> 类型。F# 版本同样简单,所以,就不在这里讨论了。重要的是,这些基本函数,是唯一需要知道任何类型的底层结构的,计算的其余部分,只需要知道类型所提供的全部基本操作(最重要的是 bind 和 return),是写查询或计算表达式所必需的。
我们定义好值标识符,即,在 F# 中表示为计算表达式(第 12.5.3 节),在 C# 中实现必要的扩展方法(第 12.5.4 节)以后,就能够轻松地处理这个类型的值了。注意,我们将使用的类型并没有实现 IEnumerable<T> 接口。查询语法和计算表达式的表示方法与序列无关,定义代码的含义,是通过一组使用 ValueWrapper <T> 类型的方法实现的。清单 12.18 的代码段,使用基本操作读两个整数,再进行计算。
清单 12.18 在 C# 和 F# 中用计算值进行计算
C# |
F# |
var v = from n in ReadInt() | [1] from m in ReadInt() | let add = n + m let sub = n – m select add * sub; [3] |
value { let! n = readInt() | [2] let! m = readInt() | let add = n + m let sub = n – m return add * sub } [4] |
在 C# 中,我们使用 from 子句提取值[1],而在 F# 中,我们是用自定义的值绑定实现的[2]。
计算完成以后,我们再把值打包到计算类型中。在 C# 中,使用 select 子句[3],在 F# 中,使用 return 基本操作[4]。
可以发现,C# 和 F# 代码的结构非常相似。这段代码并没有任何实际的用途,但是,它有助于我们理解非标准计算是如何运行的。唯一有意义的地方,是我们在 C# 写的代码,可以使用 let 子句,作为一个表达式,创建局部变量,这个子句很像 F# 中的 let 绑定,所以,整个代码就是一个表达式。
在下面的讨论中,我们将更多地关注 F# 版本,因为它能使解释原理更简单。在 C# 中的查询表达式语法是为写查询而量身定做的,所以,很难用于其他的计算类型。当我们实现了 F# 计算表达式之后,再回到 C#。
可以看到,清单 12.18 只用了两个基本操作。基本操作 bind 在调用计算基本操作时使用,基本操作 return 用于把结果打包成 ValueWrapper <int> 类型。接下来的问题可能是,F# 编译器如何使用这两个基本操作,解释计算表达式的,我们如何实现。