12.5.2 写计算
C# 查询表达式和 F# 计算表达式,可以使用函数,以非标准方式的行为(通过返回一些一元值) ,就好像返回普通的值。我们在这一节中使用的计算类型是 ValueWrapper <T>,因此,原始函数将返回ValueWrapper <T>类型的值,而不只是 T。
实现这些函数,既可以使用另外的查询或计算表达式,也可以通过直接创建计算类型的值。有些计算表达式可以封装复杂的逻辑,所以,可能很难直接创建值。在这种情况下,我们通常写少量返回计算类型的基元,使用这些基元实现其他的一切。然而,构建 ValueWrapper <T> 类型的值并不困难。下面的代码演示如何在 C# 中实现一个方法,从控制台读一个数字,并把它打包到这个计算类型内:
ValueWrapper<int> ReadInt() {
int num = Int32.Parse(Console.ReadLine());
return new ValueWrapper<int>(num);
}
这个方法从控制台读一个数字,并把它打包到 ValueWrapper <T> 类型内。F# 版本同样简单,所以,我们不在这里讨论了。重要点是,这些基元函数是,需要知道任何类型的底层结构的唯一位置。计算的其余部分,我们只需要知道这个类型支持所有的基元(最重要的是 bind 和 return),是需要写一个查询或计算的表达式。
一旦我们定义了这个值标识符,它在 F# 中表示计算表达式(第 12.5.3 节),在 C# 中实现必要的扩展方法(第 12.5.4 节),以后,就能够轻松地处理这个类型的值。注意,我们将处理的类型并没有实现 IEnumerable<T>接口。查询语法和计算表达式符号的工作独立于序列。我们将定义了代码的含义,通过实现几个方法来处理 ValueWrapper <T> 类型。清单 12.18 显示一个代码段,使用这个基元读两个整数,并用它们执行计算。
Listing 12.18 Calculating with computation values in C# and F#
C# |
F# |
var v = |
value { |
在 C# 中,我们使用 from 子句提取值,在 F# 中,同样的工作使用自定义的值绑定实现的。
一旦计算完成,我们再把这个值打包到计算类型中。在 C# 中,我们使用 select 子句,在 F# 中,使用 return 基元。
正如你所看到的,在 C# 和 F# 代码的结构是颇为相似的。这段代码没有任何实际的用途,但是,它有助于我们理解非标准计算是如何工作的。唯一有趣的是,我们可以在 C# 写代码,使用 let 子句,作为单个表达式,创建一个局部变量。这个子句的行为很像 F# 的 let 绑定,所以,整个代码就是一个表达式。
在下面的讨论中,我们将更更多地关注 F# 版本,因为,它会使解释这些是如何工作的更简单。在 C# 中的查询表达式语法是为写查询量身定做的,所以,很难用于其他的计算类型。当我们实现了 F# 计算表达式之后,会再回到 C# 。
可以看到,清单 12.18 只用了两个基元。基元 bind 是用于调用计算基元的,基元 return 用于把结果打包到 ValueWrapper <int> 类型中。接下来的问题可能是,F# 编译器如何使用这两个基元解释计算表达式,我们如何实现它们。