Haskell中的空间泄漏 - 旧编译器的错误还是我的?显然是后者

时间:2018-01-14 21:05:48

标签: haskell ghc space-leak

我最近参加了一场竞争性的编码比赛。

这个Haskell对运行ghc 7.6.3的判断系统进行了空间泄漏:

t n [] = 0
t n ('8':rest) = t (n+1) rest
t n (')':rest) = n + (t n rest)

main = getLine >>= (\l -> print (t 0 l))

比赛结束后,测试用例发布。其中一个失败案例是:(一个包含10 ^ 5个关闭的parens的文件)​​:https://cses.fi/download/1/b575d19a75bf724b50fa4a399f8187b6d6edb4ccb62bd1a774f9294969152e46

错误是

Stack space overflow: current size 8388608 bytes. Use `+RTS -Ksize -RTS' to increase it.

我也知道代码是用-O2和-Wall编译的,我认为是GHC 7.6.3。

对于我的生活,我无法在我的机器上使用GHC 8.0.2或7.10.3重现错误。

代码中是否有明显的空间泄漏?

编辑:

我测试了下面建议的代码,以及如此实现的折叠:

import Data.Foldable

t (kasit, score) '8' = (kasit+1, score)
t (kasit, score) _ = (kasit, score+kasit)

main = getLine >>= (\l -> print (snd (foldl' (t) (0, 0) l )))

爆炸模式和严格foldl'的重新实现都没有解决问题,尽管这个问题通过了更多的测试用例。原来仍然是WOMM。不可否认,这超出了通常有用的堆栈交换问题的范围,并开始看起来像旧的功课。如果不了解法官制度,这也是不可判断的。

2 个答案:

答案 0 :(得分:5)

是的,n参数表现出明显的" (对某些人来说)空间泄漏:因为它不需要检查(例如,你没有案例t 0 ... = ...),你可以在递归调用中建立增加的数量。

更好的是:

t _ [] = 0
t !n ('8':rest) = t (n+1) rest
t !n (')':rest) = n + (t n rest)

最好用foldl'来定义这个。

完全可能的是,比7.6更新版本的GHC可以更好地分析严格性并优化此代码。

有用的factoid,强制堆栈溢出可能是查找空间泄漏的有效方法(通常表现为堆使用情况):http://neilmitchell.blogspot.com/2015/09/detecting-space-leaks.html

答案 1 :(得分:3)

我认为你的最后一个案例会给你带来麻烦。你写了

t n [] = 0
t n ('8':rest) = t (n+1) rest
t n (')':rest) = n + (t n rest)

即使我们像jberryman所说的那样严格要求,

t !n [] = 0
t !n ('8':rest) = t (n+1) rest
t !n (')':rest) = n + (t n rest)

第三种情况不是尾递归。我们该如何解决这个问题?只需添加另一个累加器,表示最后添加的数量。

t n0 xs = t' 0 n0 xs
  where
    t' !acc !_n [] = acc
    t' acc n ('8':rest) = t' acc (n + 1) rest
    t' acc n (')':rest) = t' (acc + n) n rest