一些语言(如Haskell、Clojure、Scheme等)具有惰性求值。惰性求值的一个“卖点”是无限数据结构。这有什么好处?有哪些情况下能够处理无限数据结构明显具有优势的例子?
一些语言(如Haskell、Clojure、Scheme等)具有惰性求值。惰性求值的一个“卖点”是无限数据结构。这有什么好处?有哪些情况下能够处理无限数据结构明显具有优势的例子?
这里有两个例子,一个大的,一个小的:
Why Functional Programming Matters 一书中的John Hughes给出了一个很好的例子,关于一个国际象棋游戏。 国际象棋游戏的移动树实际上并不是无限的,但足够大以至于可以视为无限(称之为“近似无限”)。在严格语言中,您无法将其视为一棵树,因为没有足够的空间来存储整个树。但在惰性语言中,您只需定义该树,然后定义一个"nextMove"函数来遍历它直到必要的程度。惰性求值机制会处理细节。
小的例子就是为列表中的每个项分配一个索引号,使["foo", "bar", "baz"]变成[(1,"foo"), (2,"bar"), (3,"baz")]。 在严格语言中,您需要一个循环来跟踪最后一个索引并检查是否已到达结尾。 在Haskell中,只需说:
zip [1..] items
zip的第一个参数是一个无限列表。你不需要预先计算它需要多长。
(map cons (iota (length items)) items)
,其中iota
来自srfi-1。你不需要循环。 - knivil以下是我能想到的几个优点:
O(n^2)
到O(n log n)
),那么这可以是一个巨大的胜利。有一种经典的纯记忆化策略:
fib = (map fib' [0..] !!)
where
fib' 0 = 0
fib' 1 = 1
fib' n = fib (n-1) + fib (n-2)
fib'
函数映射到无限列表上,以构建fib
的所有值的表格。 好了!廉价、易于记忆。iota count begin step = let xs = begin:map (+step) xs
in take count xs
-- or, alternately
iota count begin step = take count $ map ((+begin).(*step)) [0..]
如果我滥用惰性,也可以为非空列表编写length
:
len = fst . last . zip [1..]
-- or even handling empty lists
len = fst . last . zip [0..] . (undefined:)
考虑Prelude中定义的强大而优雅的iterate
函数。
iterate f x = x : iterate f (f x)
[x, f x, f (f x), f (f (f x)), ...]
。我可以用iterate
来编写iota
:iota count begin step = take count $ iterate (+step) begin
懒惰编程是一种优雅的方式。它并不是唯一的方式,熟悉C或Java的人们肯定会喊出“但我不需要懒惰,我可以只是_”,这是正确的。如果您的语言是图灵完备的,那么也可以完成。但是,懒惰编程可以非常优雅。
上个月我有一个很好的应用场景。在复制对象时,我需要一个生成唯一名称的生成器。这意味着,生成器会接收原始名称X
,并为副本生成一个新名称。它通过附加文本来实现此目的。
X - copy
X - copy (2)
X - copy (3)
...
[(0, 4), (1, 3.5), (3.1, 3.2), ...]
可以用pi
来表示。通过内置的惰性,对这个表示进行操作变得很容易。