假设您有一个非常确定的算法来生成列表,例如inits
中的Data.List
。有没有办法让Haskell编译器能够最优地对该算法执行“索引”操作而不实际生成所有中间结果?
例如,inits [1..] !! 10000
非常慢。编译器能否以某种方式推断出inits
会在第10000个元素上产生什么而不进行任何递归等等?当然,同样的想法可以超越列表。
编辑:虽然inits [1..] !! 10000
是常量,但我想知道某些算法上的任何“类索引”操作。例如,可以对\i -> inits [1..] !! i
进行优化,以便不会执行[或最小]递归来达到任何i
的结果吗?
答案 0 :(得分:7)
是和否。如果你看一下Data.List.inits
的定义:
inits :: [a] -> [[a]]
inits xs = [] : case xs of
[] -> []
x : xs' -> map (x :) (inits xs')
你会看到它是递归定义的。这意味着结果列表的每个元素都构建在列表的前一个元素上。因此,如果你想要任何第n个元素,你必须构建所有n-1个前面的元素。
现在你可以定义一个新功能
inits' xs = [] : [take n xs | (n, _) <- zip [1..] xs]
具有相同的行为。如果您尝试使用inits' [1..] !! 10000
,它会很快完成,因为列表的连续元素不依赖于先前的元素。当然,如果你实际上是在尝试生成一个inits列表而不是一个单独的元素,那么这将会慢得多。
编译器必须知道很多信息才能从inits
这样的函数中优化掉递归。也就是说,如果一个函数确实是“非常确定的”,那么以非递归的方式重写它应该是微不足道的。