首先,您可能需要考虑简化谓词函数,因为它们不必要地冗长。以我的拙见,这是等效的并且样式更好:
fun even n = n mod 2 = 0
fun odd n = n mod 2 <> 0
关于流数据类型
由于 SML 有严格的评估,传统的列表是行不通的。您必须从定义自己的流数据类型开始。流是延迟列表。
您对 fibs 函数的定义似乎暗示了这种数据类型的存在:
datatype 'a stream = Empty | Cons of 'a * (unit -> 'a stream)
如您所见,类型元素'a stream
可以是Empty
或Cons
包含某个类型值'a
和能够生成下一个流元素的函数。
这样,在我们实际调用该函数之前,我们可以在评估第二个元素时有所不同。
自然数的无限流
例如,您可以定义一个无限的自然数流,如下所示:
fun from n = Cons(n, fn () => from(n +1))
val naturals = from(1)
这里的 naturals 是一个包含所有自然数的无限流。你可以看到 Cons 只包含第一个元素,而第二个元素是一个函数,当计算时,它可以生成另一个流元素,这次包含 2,依此类推。
流函数库的需求
显然,您不能将此数据结构与传统的列表函数一起使用。您需要编写自己的函数来处理这种数据类型。
例如,您可以编写自己的take
函数,从一个流中取出 n 个元素,从原始流中创建一个有限流:
fun take n xs =
if n = 0
then Empty
else case xs of
Empty => Empty
| Cons(h,t) => Cons(h, fn() => take (n-1) (t()))
或者,您可以创建自己的filter
函数以从流中过滤元素,从而在此过程中创建一个新流。
fun filter f xs =
case xs of
Empty => Empty
| Cons(h,t) => if f(h)
then Cons(h, fn () => filter f (t()))
else filter f (t())
您需要一个将zip
两个流的元素压缩到另一个压缩流中的函数,如下所示:
fun zip(xs,ys) =
case (xs,ys) of
(Empty,_) => Empty
| (_, Empty) => Empty
| (Cons(h1,t1), Cons(h2,t2)) => Cons( (h1,h2), fn () => zip(t1(),t2()))
您甚至可能希望拥有一个将有限流转换为列表的函数,仅用于调试目的,因为列表在 REPL 中更易于阅读:
fun toList xs =
case xs of
Empty => []
| Cons(h,t) => h::toList(t())
例如:
toList (take 10 (from 1))
将获得前 10 个自然数作为列表。
filter odd
将产生一个只从 int 流中获取奇数元素的函数。
filter even
将产生一个仅从 int 流中获取偶数元素的函数。
- ETC,
斐波那契数列的无限流
假设有无限的斐波那契数流:
fun fibonacci() =
let
fun fib(a,b) = Cons(a+b, fn() => fib(b,a+b))
in
Cons(0, fn() => fib(0,1))
end
您现在可以使用filter odd
andfilter even
函数仅过滤掉偶数或奇数斐波那契数,然后使用zip
具有这两个结果的函数来获得(奇数,偶数)斐波那契数的压缩流,并从生成的流中,您可以take
取出第一个10个元素...
val fibs = fibonacci()
val evens = filter even
val odds = filter odd
val zipped = zip(evens fibs, odds fibs)
...您最终可以变成这样的列表:
val r = toList (take 10 zipped)