我正在尝试编写一个findIndexBy
返回由排序函数在列表中选择的元素的索引。此功能相当于对列表进行排序并返回顶部元素,但我想实现它以能够处理没有大小限制的列表。
findIndexBy :: (Ord a) => (a -> a -> Bool) -> [a] -> Integer
findIndexBy f (x:xs) = findIndexBy' xs x 1 0
where
findIndexBy' [] _ _ i = i
findIndexBy' (x:xs) y xi yi = if f x y
then findIndexBy' xs x (xi + 1) xi
else findIndexBy' xs y (xi + 1) yi
通过这个实现,我Stack space overflow
在处理大列表时得到一个,如下例(简单):
findIndexBy (>) [1..1000000]
我知道应该有更优雅的解决方案来解决这个问题,并且我有兴趣了解最惯用和最有效的解决方案,但我真的很想了解我的功能出了什么问题。
我可能错了,但我认为我的实现findIndexBy'
是基于终端递归的,所以我真的不明白为什么编译器似乎没有优化尾调用。
我认为这可能是由于 if/then/else 并且还尝试了以下操作,这会导致相同的错误:
findIndexBy :: (Ord a) => (a -> a -> Bool) -> [a] -> Integer
findIndexBy f (x:xs) = findIndexBy' xs x 1 0
where
findIndexBy' [] _ _ i = i
findIndexBy' (x:xs) y xi yi = findIndexBy' xs (if f x y then x else y) (xi + 1) (if f x y then xi else yi)
有没有一种简单的方法可以让编译器显示(不)执行尾调用优化的位置?
作为参考,下面是我在 Clojure 中编写的等效函数,我现在正尝试移植到 Haskell:
(defn index-of [keep-func, coll]
(loop [i 0
a (first coll)
l (rest coll)
keep-i i]
(if (empty? l)
keep-i
(let [keep (keep-func a (first l))]
(recur
(inc i) (if keep a (first l)) (rest l) (if keep keep-i (inc i)))))))
有关信息,之前引用的 Haskell 代码是使用该-O3
标志编译的。
[在leventov回答后编辑]
该问题似乎与惰性评估有关。虽然我发现了$!
and seq
,但我想知道使用它们修复原始代码时的最佳实践是什么。
我仍然对依赖Data.List
.
[编辑]
最简单的解决方法是在语句yi `seq`
之前添加第一个片段。if