首页 > 代码库 > 11.3.4 F# 中的延迟值

11.3.4 F# 中的延迟值

11.3.4 F# 中的延迟值

 

F# 中的延迟值(lazy value)是通过延迟计算表示的,就是说,只有在值需要时才进行计算。在上一节,我们用 C# 函数实现了类似的功能,而延迟值自动只计算一次,并能记住结果。

探索此功能的最佳方法是在 F# Interactive 中进行。清单 11.16 用脚本进行了演示。

[

清单编号的错误再次出现了.

]

清单 11.16 介绍延迟值 (F# Interactive)

> let foo(n) =

    printfn "foo(%d)" n

    n <= 10;;

val foo : int ¨C> bool

 

> let n = lazy foo(10);;                 | [1]

val n : Lazy<bool> = Value is notcreated.  |

 

> n.Value;;    <-- 调用 foo,得到结果

foo(10)          | [2]

val it : bool = true  |

 

> n.Value;;    <-- 立即返回结果

val it : bool = true    [3]

 

我们首先写一个类似于 C# 的 Foo 方法的函数,它通过写到控制台跟踪计算的时间;第二个命令使用 F# 的 lazy 关键字[1]。如果表达式用lazy 标记,那么,这个表达式不会立即计算,会被打包成延迟值。从输出可以看到,foo 函数还未调用,创建的值为lazy<bool> 类型,表示可以计算出布尔值的延迟值。

在下一行,可以看到这个延迟值有一个成员,Value [2],这个属性执行延迟计算,在这里,就是调用 foo 函数。最后一个命令再次访问 Value [3],不会重新计算。如果看一下输出,就会发现 foo 函数并未调用。这就是说,我们可以清楚地看到,Lazy<‘T> 是可变类型。如果我们使用无副作用的纯函数式的函数,作为其参数,就不能观察到这个现象。

在前几章,我们已经强调过用这种函数式方式查看数据类型,已经发现,在处理类型时,知道需要哪种操作,是很有用的。现在,我们就以这种观点来审视一下延迟值。

 

使用操作指定延迟值

 

如果 F# 语言没有 lazy 关键字,并且不允许写有属性的对象,可能会需要两个操作,一个用于构建延迟值,另一个提取实际值:

 

val create : (unit -> ‘T) ->Lazy<‘T>

val force : Lazy<‘T> ¨C> ‘T

 

可以看到,create 操作的参数是一个函数值,包装在 Lazy<‘T> 值当中。在函数式编程中,存在表示延迟计算还有其他类型,所以,当函数的参数为 unit -> ‘T 类型的函数,返回类型为泛型时,类型可能表示成延迟计算。force 操作的签名甚至更简单,只是简单地从打包它的类型中提取实际值。这个签名并没有告诉我们实际值是如何打包的,但是,因为有 force 操作,我们总是可以提取它。

在下一章我们将看到,在函数式编程中,就能够处理基本操作而言,类型的这种抽象描述是有用的。虽然 F# 提供了处理延迟值的方法,比使用函数更方便,但是,了解基本操作(primitive operations)还是很有帮助的。

 

我们开始讨论延迟值的动机,是不会自己实现逻辑或者运算符,计算右手边的参数值,要看是否需要,何时需要。现在,我们将再尝试用延迟值的新知识武装一下。

11.3.4 F# 中的延迟值