以下简短的Haskell程序旨在计算文件中的项目列表。使用foldl'的版本可以正常工作,但是使用ST Monad的版本可以提供堆栈空间溢出消息。显然,这里存在某种形式的空间泄漏,但是我一直无法解决。真正有趣的部分是ST monad应该进行就地更新,并且不应让资源这样增长,尽管这可能仅与主内存有关,而与堆栈空间无关。有人可以解释这里发生了什么吗?

import Control.Monad
import Data.List
import Control.Monad.ST
import Data.STRef

--count items using foldl'
countFold :: Num a => [b] -> a
countFold = foldl' (\a _ -> a+1) 0

-- count items using the ST monad
-- derived fromt the sumST example on http://www.haskell.org/haskellwiki/Monad/ST
-- only using +1 instead of adding the values
countST :: Num a => [b] -> a
countST xs = runST $ do

    n <- newSTRef 0

    forM_ xs ( \_ -> modifySTRef n (+1) )

    readSTRef n



main = do

    mydata <- readFile "data_files/values_1000000.num"
    let trainingdata = lines mydata

    -- this works just fine
    --(putStrLn (show (countFold trainingdata)))

    -- This fails with the message:
    --   Stack space overflow: current size 8388608 bytes.
    --   Use `+RTS -Ksize -RTS' to increase it.
    (putStrLn (show (countST trainingdata)))

更新:感谢您的回答和评论。我想我知道这里发生了什么。 ModifySTRef'是4.6版中的新功能,可以很好地解决该问题,并提供了有人提到的解释。我正在使用Data.STRef的4.5版本,这在Ubuntu中似乎是标准的,并且既不包含解释也不包含ModifySTRef'。

查看4.6软件包的版本和函数,不同之处在于它使用seq来确保函数f严格应用(并存储在x'中):
modifySTRef :: STRef s a -> (a -> a) -> ST s ()
modifySTRef ref f = writeSTRef ref . f =<< readSTRef ref

modifySTRef' :: STRef s a -> (a -> a) -> ST s ()
modifySTRef' ref f = do
    x <- readSTRef ref
    let x' = f x
    x' `seq` writeSTRef ref x'

因此解决该问题的另一种方法是将函数的代码复制到我自己程序的空间中的新名称,然后将seq应用于泄漏区域,这是我将来可能会使用的一个很好的通用技巧。感谢大家帮助我解决这个问题。

最佳答案

这是a classic space leak
modifySTRef不会将其函数参数的应用结果强加给状态。
实际上,您无法编写其参数函数来确保严格性。

请使用modifySTRef'

10-07 13:29
查看更多