首页 > 代码库 > 11.3.3 用函数模拟延迟计算

11.3.3 用函数模拟延迟计算

11.3.3 用函数模拟延迟计算

 

在F# 和C# 中计算顺序是提前的:作为给函数参数使用的表达式,在函数自身开始执行之前就计算好了。在C# 和F# 中,我们可以使用函数值模拟延迟计算,另外,F# 甚至有一个专门的关键字,支持延迟计算。

但首先,对于提前计算规则有一个例外,你肯定知道,并经常使用,但只是因为太常用,反而可能没有意识到它的特别。有些特定的C# 运算符,比如,逻辑或(||)、逻辑与(&&)、条件运算符(?:),以及空合并运算符(null-coalescing,??),能实现短路径(short-circuiting)行为,只计算那些计算结果需要的操作数。这样,我们不能很容易地实现与|| 运算符有同样行为的Or 方法:

 

if (Foo(5) || Foo(7))

  Console.WriteLine("True");

if (Or(Foo(5), Foo(7)))

  Console.WriteLine("True");

 

比方说,当参数小于10 时,Foo 方法返回true。这样,当计算Foo(5) 值时,内置的|| 运算符知道整体的结果肯定是true,因此,就不再计算Foo(7);相反,如果调用Or 方法,两个参数值都在方法调用前进行计算。那么,有没有办式写出这样的代码,使得如果表达式Foo(5) 的值为true 时,就不计算Foo(7) 呢?

使用函数值,可能是一个答案,即,把方法的参数类型bool,改成Func<bool>。这样,后面的代码需要这个值时,可以执行这个函数,它会再回过来计算这个表达式的值。在清单11.15 中可以看到,使用这种方法写的或操作符(现在称为LazyOr)。

 

清单11.18 使用函数的延迟或运算符(C#)

bool Foo(int n) {

  Console.WriteLine("Foo({0})",n);    [1]

  returnn <= 10;

}

bool LazyOr(Func<bool> first, Func<bool>second) {    [2]

  if (first())return true;  <--计算 first 参数

  if (second()) return true;   <-- 计算 second 参数

  returnfalse;

}

 

if (LazyOr(() => Foo(5), () => Foo(7)))  <-- 只输出 Foo(5)

  Console.WriteLine("True");

 

我们说明问题,使用Foo 方法输出到屏幕[1],因此,可以跟踪调用的过程。原始的或运算符的参数值,现在打包在lambda 函数的里面,当方法被调用时,它的参数值提前计算,但是,而现在的参数值是函数;lambda 函数内的表达式,要等到函数被调用时,才计算。

看一下LazyOr 方法[2],在需要访问布尔值,即将要计算的地方,我们调用由参数值提供的函数。如果ifrst 函数返回true,则LazyOr 方法将立即返回True,second 函数根本不会调用。这样,代码的行为就像内置的逻辑或运算符一样了。

假设我们需要访问参数值不止一次,那么,要多次调用函数吗?这听起来就不像是个非常有效的解决方案,因此,我们可能要把结果保存起来。这在F# 中,实现起来很简单,用到一个叫延迟值(lazy values)的功能。我们会先看一些F# 代码,然后,用C# 实现同样的行为;之后,我们将看一个示例应用程序,可能会对你在自己的代码中使用这种技术提供一些帮助。

11.3.3 用函数模拟延迟计算