F#基础教程 惰性求值
惰性求值常用于函数式编程。该理论表述为,如果语言无副作用,则编译或运行时可以自由选择表达式的求值顺序。如你所知,F#中允许函数产生副作用,所以它不能在编译或运行时有一个自做主张的函数求值。因此,F#可以说有严格的求值顺序,或者说是严格的语言。你仍然可以利用惰性求值的优势,但必须明确哪些计算可以延迟,也就是,惰性方式的计算。使用关键字lazy来延迟计算,也就是,调用惰性计算时。lazy内的表达式保持不计算,直到评估。它被强制生效函数在惰性模块里。当强制函数是应用于一个特定惰性表达式,值的计算;那么结果被缓存,其后调用强制函数将返回缓存的值,不管它是什么,即便这意味着引发异常。下面显式一个简单的使用惰性求值的程式码:
#light
let lazyValue = lazy ( 2 + 2 )
let actualValue = Lazy.force lazyValue
print_int actualValue
print_newline()
在第一行,你延迟了一个简单表达式的计算。下一行调用计算。然后,你打印值,该值已经被缓存,因此任何副作用,值的计算仅发生在第一次强制lazy值时。这个很容易理解。在下面的例子中,你将创建一个有副作用的lazy值,其计算方法是:输出到控制台。为了显式这种副作用只发生一次,你强制两次值,明显看到写入控制台的结果只发生一次。
#light
let lazySideEffect =
lazy
( let temp = 2 + 2
print_int temp
print_newline()
temp )
print_endline "Force value the first time: "
let actualValue1 = Lazy.force lazySideEffect
print_endline "Force value the second time: "
let actualValue2 = Lazy.force lazySideEffect
执行结果:
Force value the first time:
4
Force value the second time
在集合中使用惰性更有用。一个lazy集合的想法是,根据计算返回需要的元素。一些集合类型也使用计算结果的缓存,所以没有必要重新计算元素。F#提供了LazyList集合类型,缓存计算结果是非常有用的函数式编程。第二个lazy集合时seq类型,是BCL’s IEnumerable类型的简写。这类似于LazyList的作用但不缓存计算结果。LazyList和seq值是分别创建和操作使用LazyList和seq模块的函数。许多其他值也与seq类型兼容,包括大多数集合类型。
下面的例子演示如何使用LazyList模块。可能最重要的函数和最难以理解是unfold。这个函数允许你创建一个lazy列表。什么使它如此复杂?你必须提供一个函数,在反复的计算中生成列表的元素。这个函数传递给LazyList.unfold的可以是任何类型的参数,但必须返回一个option类型。一个option类型是一个联合类型,但无论是None或者Some(x),其中x是任意类型的值。None用来表示列表的结束。Some构造函数必须包含一个元组。在元组里第一项代表的值将成为列表的第一个值。在元组里第二项代表的值将传递给下一次的函数使用。你可以认为这个值作为一个累加器。
下一个例子说明函数如何工作。标识符lazyList将包含三个值。如果传递到函数的值小于13,追加使用此值形成的列表元素到列表,然后值加1并传递给列表,亦即传递给函数的下一次调用(原文比较绕口,大概意思是列表包含一个函数,而传递给列表的参数,将被列表传递给函数用于生成下一个元素,函数也可以传递元素给列表)。如果值大于或等于13,返回None结束列表。显式列表,你可以使用函数display,一个简单的递归函数,处理列表的头部(head),然后递归处理列表的尾部(tail)。
#light
let lazyList =
LazyList.unfold
(fun x ->
if x < 13 then
Some(x, x + 1)
else
None)
10
let rec display l =
match l with
| LazyList.Cons(h,t) ->
print_int h
print_newline ()
display t
| LazyList.Nil ->
()
display lazyList
执行结果:
10
11
12
Lazy列表也可以表示无穷列表。一个无穷列表不能用典型的列表表示,因为可用内存的限制。下一个例子创建fibs,是Fibonacci数列的无穷列表;它采用了Seq模块,尽管也可以使用LazyList模块,因为unfold函数都以同样的方式工作。为方便显式结果,使用Seq.take转为一个包含前20项的F#列表,但你可以使用F# bigint类型的整数计算出更多Fibonacci 数。
#light
let fibs =
Seq.unfold
(fun (n0, n1) ->
Some(n0, (n1, n0 + n1)))
(1I,1I)
let first20 = Seq.take 20 fibs
print_any first20
执行结果:
[1I; 1I; 2I; 3I; 5I; 8I; 13I; 21I; 34I; 55I; 89I; 144I; 233I; 377I; 610I; 987I;
1597I; 2584I; 4181I; 6765I]
要真正体现出惰性求值的强大,这些例子太简单了。你将在本书的其它地方看到惰性求值,特别在第七章,你将了解如何在用户接口编程中使用惰性求值,使得更多用户接口,直到真正需要时再响应,确保不需要时不发生计算。