I am struggling to understand why the following attempts to find a minimum element in STArray lead to stack space overflow when compiled with ghc
(7.4.1, regardless of -O level), but work fine in ghci
:
import Control.Monad
import Control.Monad.ST
import Control.Applicative
import Data.Array.ST
n = 1000 :: Int
minElem = runST $ do
arr <- newArray ((1,1),(n,n)) 0 :: ST s (STArray s (Int,Int) Int)
let ixs = [(i,j) | i <- [1..n], j <- [1..n]]
forM_ ixs $ \(i,j) -> writeArray arr (i,j) (i*j `mod` 7927)
-- readArray arr (34,56) -- this works OK
-- findMin1 arr -- stackoverflows when compiled
findMin2 arr -- stackoverflows when compiled
findMin1 arr = do
es <- getElems arr
return $ minimum es
findMin2 arr = do
e11 <- readArray arr (1,1)
foldM (\m ij -> min m <$> readArray arr ij) e11 ixs
where ixs = [(i,j) | i <- [1..n], j <- [1..n]]
main = print minElem
Note: switching to STUArray
or ST.Lazy
doesn't seem to have any effect.
The main question though: What would be the proper way to implement such "fold-like" operation over big STArray
while inside ST
?
The big problem in
findMin1
isgetElems
:Using
sequence
on a long list is a common cause for stack overflows in monads whose(>>=)
isn't lazy enough, sincethen it has to build a thunk of size proportional to the length of the list.
getElems
would work with theControl.Monad.ST.Lazy
, but then the filling of the array withcreates a huge thunk that overflows the stack. For the strict
ST
variant, you need to replacegetElems
with something that builds the list withoutsequence
or - much better - compute the minimum without creating a list of elements at all. For the lazyST
variant, you need to ensure that the filling of the array doesn't build a huge thunk e.g. by forcing the result of thewriteArray
callsThe problem in
findMin2
is thatis lazy in
m
, so it builds a huge thunk to compute the minimum. You can easily fix that by usingseq
(or a bang-pattern) to make it strict inm
.Usually, you'll use the strict
ST
variant (and for types likeInt
, you should almost certainly useSTUArray
s instead ofSTArray
s). Then the most important rule is that your functions be strict enough. The structure offindMin2
is okay, the implementation is just too lazy.If performance matters, you will often have to avoid the generic higher order functions like
foldM
and write your own loops to avoid allocating lists and control strictness exactly as the problem at hand requires.