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]

      要真正体现出惰性求值的强大,这些例子太简单了。你将在本书的其它地方看到惰性求值,特别在第七章,你将了解如何在用户接口编程中使用惰性求值,使得更多用户接口,直到真正需要时再响应,确保不需要时不发生计算。

posted @ 2011-12-07 16:05  银河系漫游指南  阅读(729)  评论(0编辑  收藏  举报