有些语言(Haskell,Clojure,Scheme等)有懒惰的评价。懒惰评估的“卖点”之一是无限数据结构。这有什么好处的?能够处理无限数据结构的情况的一些例子显然是有利的吗?
答案 0 :(得分:20)
以下是两个例子,一个大一小:
国际象棋比赛中,约翰·休斯(John Hughes)有一个很好的例子。{p> Why Functional Programming Matters。国际象棋游戏的移动树实际上并不是无限的,但它足够大以至于它可能是无限的(称之为“近无限”)。在严格的语言中,您实际上不能将其视为树,因为没有足够的空间来存储整棵树。但是在惰性语言中,您只需定义树,然后定义“nextMove”函数,以便在必要时遍历它。懒惰的评估机制负责细节。小例子只是将索引号与列表中的每个项相关联,因此[“foo”,“bar”,“baz”]变为[(1,“foo”),(2,“bar”) ),(3,“baz”)]。在严格的语言中,您需要一个循环来跟踪最后一个索引,并检查您是否在最后。在Haskell中你只需说:
zip [1..] items
zip的第一个参数是无限列表。您不需要计算出提前需要多长时间。
答案 1 :(得分:14)
我能想到的一些优点:
O(n^2)
到O(n log n)
),那么这可能是一个很大的胜利。答案 2 :(得分:7)
有规范的纯记忆策略:
fib = (map fib' [0..] !!)
where
fib' 0 = 0
fib' 1 = 1
fib' n = fib (n-1) + fib (n-2)
我们将fib'
函数映射到无限列表,以构建所有值fib
的表。瞧!便宜,易记忆。
当然,这在参数中具有线性查找时间。您可以用无限trie替换它以获得对数查找时间。比照data-inttrie
答案 3 :(得分:6)
我打算对@ knivil的计划发表评论。相反,我会把它作为另一个答案。
懒惰的数据结构不是唯一的方式来完成大多数任务。这可能会激怒Pythonistas。但我相信程序员可以选择使用哪种技术。懒惰的技术是强大而优雅的。
Knivil提到使用Scheme的iota
。看看编写完整方法(包含所有3个参数)是多么容易,依赖于懒惰:
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)), ...]
。我可以根据iota
:
iterate
iota count begin step = take count $ iterate (+step) begin
懒惰的方法是一种优雅的编程方式。这不是唯一的方式,习惯于C或Java的人肯定会喊出“但我不会需要懒惰,我只能_”,而且他们是正确的。如果您的语言是Turing-complete,则可以完成。但懒惰可以如此优雅。
答案 4 :(得分:2)
好吧,上个月我有一个很好的用例。复制对象时,我需要一个用于唯一名称的生成器。这意味着,生成器采用原始名称X
,并为副本生成新名称。它通过附加像
X - copy
X - copy (2)
X - copy (3)
...
只要在同一组内的对象集中未使用该名称。使用“无限数据结构”(无限数组的字符串)代替简单循环有一个好处:如果名称已在使用中,您可以将名称生成部分完全与测试分开。因此,我可以将生成器函数重用于不同类型的对象,其中每个对象类型的使用中测试略有不同。
答案 5 :(得分:2)
无限数据结构提供(可计算的)实数的优雅表示。例如,像
这样的无限列表[(0, 4), (1, 3.5), (3.1, 3.2), ...]
可以代表pi
。由于内置的懒惰,操作这种表示变得容易。