函数式编程笔记 12
Stream
想要找出从1000到10000间第二个素数,可以这样写:
|
|
这性能上很不好,只需要第二个素数,但找出了1000到10000的所有素数。
可以用流来改进它,流和列表类似,但是流只在需要时才会被求值。
流是由一个常量Stream.empty
和一个构造器Stream.cons
定义:
|
|
来看一个和(lo until hi).toStream
类似的函数:
|
|
和它的List
版本比较:
|
|
它们的区别主要在求值方式:
listRange(start, end)
会生成一个包含从end
到start
元素的列表streamRange(start, end)
会生成一个以start
作为头元素的流- 剩下的元素仅在需要时才会被求值,需要是指在流上调用
tail
时
流支持大部分列表上的操作,之前找素数的代码可以这样写:
|
|
流和列表操作上最大的区别是连接符,x :: xs
会生成列表,而x #:: xs
会生成流。#::
也可以用在模式中。
这是流的trait:
|
|
这是流的简单实现:
|
|
可以看到用了call-by-name参数=>
,因此不会立刻求值。
这是filter
方法的实现:
|
|
懒求值(Lazy Evaluation)
之前的实现有严重的性能问题,如果流的tail
方法被调用多次,对应的流也会被多次求值。这个问题可以通过保存已计算出的值来解决,这种解决方法是可靠的,因为在纯函数式编程中无论何时求值表达式的值都是不变的。
这种方法叫做懒求值,相对的是by-name求值(每次都会被重计算)和直接(strict)求值(普通参数和val
定义)。
Haskell默认使用懒求值,Scala默认使用直接求值,但也允许通过lazy val
来应用懒求值:
|
|
练习:下面的代码的结果是什么?
|
|
答案是xzyz。
之前的Stream
实现可以改成懒求值:
|
|
无限流
例如,从给定整数开始的所有整数:
|
|
例子:用筛法(The Sieve of Eratosthenes)求素数。
|
|
回顾之前用牛顿法求平方根中的sqrt
函数,可以用Stream
来重构它:
|
|
总练习:倒水问题
有一个水槽和若干无刻度只知道容量的杯子,如何操作才能倒出需要的水量?
简单规划一下:
- 杯子Glass:
Int
- 状态State:
Vector[Int]
- 操作Moves:
- 清空
Empty(glass)
- 装满
Fill(glass)
- 从一个杯子倒到另一个杯子
Pour(from, to)
- 清空
代码:
|
|
当代码变长时注意:
- 尽可能地命名
- 把操作放在自然的作用域里
- 保持可优化性
更多资源
请直接参考网站Functional Programming Principles in Scala。
-完-