惰性求值
惰性求值也叫按需调用,是一个演算策略,延迟一个表达式的演算,直到需要用到演算值的时候再演算,同时也避免了重复演算。
惰性求值的好处包括:
避免不必要的计算,提升性能。可以构造以构造一个无限的数据结构可以定义控制流的抽象类。
惰性求值由于仅仅在需要的时候才求值,所以它可以缩小内存的访问范围。但是惰性求值处理异常,输入输出的时候却有困难,因为代码操作的顺序无法得知,惰性求值可能引起内存泄漏。
惰性求值和函数式编程关系密切。如果没有语言中没有副作用,编译器或可以自由的选择表达式的演算顺序。F#允许函数有副作用,所以在函数演算时,编译器不可能自由选择,因此,可以说F#有严格的演算顺序,是一个严格的语言。你仍然可以利用惰性求值,但是必选显式表面哪些计算可以延迟,也就是哪些按照惰性的方法来演算。
惰性求值的相对的就是及早求值(Eager evaluation),也称严格求值。
使用关键字lazy来延时计算,延迟计算的表达式一直不演算,直到显式的对其使用force函数。当force函数用于由于惰性表达式,值就马上计算,结果会被缓存起来。后面再调用force函数,则返回这个缓存值,即使可能引起异常。
下面的代码演示了惰性求值。
let lazyValue = lazy ( 2 + 2 ) let actualValue = Lazy.force lazyValue printfn "%i" actualValue
第一行延迟计算了一个简单表达式,下一行强制计算,最终,打印出值。
这个值被缓存了,所以这个值仅在第一次强制惰性求值的时候计算,这很容易演示,如下:
let lazySideEffect = lazy ( let temp = 2 + 2 printfn "%i" temp temp ) printfn "Force value the first time: " let actualValue1 = Lazy.force lazySideEffect printfn "Force value the second time: " let actualValue2 = Lazy.force lazySideEffect
上例中,强制计算2次值,但是此副作用值发生一次。运行结果如下:
惰性求值在操作集合的时候很有用,惰性集合的概念就是集合中的元素仅在需要的时候才计算。一些集合类型也会缓存计算结果,所以也不需要重复计算元素。
注意,此处用 Lazy.force因为作者可能使用的
OCaml,在F#从 OCaml 风格转到C#风格之前。现在用x.Value
或者x.Force()替代。由于某些原因
F#现在在PowerPack中。F# PowerPack是由微软的F#团队提供的编译器和核心库。http://fsharppowerpack.codeplex.com/
创建惰性集合,最重要也是最难的可能要数unfold。此函数允许你建立惰性List。难点在于你必须提供一个函数,此函数用于重复计算以提供元素。这个传递给Seq.unfold的函数可以接受任何类型的参数,并且必须返回一个option类型。option类型是一个union类型,可以是None或者Some(x),x是任何类型的值。None代表list的结束。Some 结构必须包含一个元组。第一个元素是list中的第一个值,第二个值是下一次调用的时候要传给函数的值。你可以认为这个值是一个累加器。
看一个例子就明白,标识符lazyList,如果传递给函数的值小于13,追加到list,然后把值加1传递给list。这个值将是下一次调用的时候传递给函数的。如果这个值大于等于12,返回None,终结这个list。
// the lazy list definition let lazyList = Seq.unfold (fun x -> if x < 13 then // if smaller than the limit return // the current and next value Some(x, x + 1) else // if great than the limit // terminate the sequence None) 10 // print the results printfn "%A" lazyList
结果就是 10,11,12
Sequences用来表示list,如果内存无限多,那么也就可以有无限多元素。下面的例子演示创建一个Fibonacci数列。由于使用了bigint,不受32-bit整型限制,可以计算更多的数字。为了演示方便,使用函数 Seq.take返回前20个元素到F#的list中。
// create an infinite list of Fibonacci numbers let fibs = Seq.unfold (fun (n0, n1) -> Some(n0, (n1, n0 + n1))) (1I,1I) // take the first twenty items from the list let first20 = Seq.take 20 fibs // print the finite list printfn "%A" first20
计算结果:
[1I; 1I; 2I; 3I; 5I; 8I; 13I; 21I; 34I; 55I; 89I; 144I; 233I; 377I; 610I; 987I;
1597I; 2584I; 4181I; 6765I]