I want to learn using the ST-Monad. Therefore I want to rewrite a bit of code computing for every integer – up to a limit – the list of all its proper divisors. The result should be an array and the entry of index 'n' should be the list of it's proper divisors.
It is done by computing for each Integer 'n' a list 'l' of its multiples and adding for each multiple 'm' out of 'l' at the index 'm' it's divisor 'n' to the list.
Here's the code I want to modify:
properDivisorsOf' :: forall a. (Integral a, Ix a) => a -> Array a [a]
properDivisorsOf' limit =
  let generate :: (Integral a, Ix a) => a -> Array a [a] -> Array a [a]
      generate n acc
        | n > (limit `div` 2) = acc
        | otherwise           =
              let acc' = acc // [(i, n : (acc ! i)) | i <- [2*n, 3*n .. limit]]
              in  generate (n + 1) acc'
  in generate 1 (array (1, limit) [(i, [])| i <- [1..limit]])
And that's the way how I try it:
properDivisorsOf :: forall a. (Integral a, Ix a) => a -> Array a [a]
properDivisorsOf limit =
  let result :: ST s (STArray s a [a])
      result = newArray (1, limit) [] -- In the beginning for every number: no divisors known (empty list) 
      update (index, divisor) = do
        l <- readArray result index -- extracting list of divisors of number 'index'
        let u = divisor : l
        writeArray result index u   -- and adding 'divisor' to the list
      content :: [(a, a)]
      content = do
        n <- [1 .. (limit `div` 2)]
        multiple <- [2*n, 3*n .. limit]
        return (multiple, n)
      doUpdate = map update content -- update result for all multiples (in content)
in runSTArray result
Unfortunatley it is not compiling and the error message doesn't say anything to me. I have two questions:
- Why doesn't it compile? How can I extract the entry correctly?
- How would an experienced Haskell-Programm solve this problem under the restriction that he has to work with the ST-Monad (for efficiency purposes)
EDIT: Compiler message
    Couldn't match expected type `[t0]'
            with actual type `STArray i0 a [a]'
    In the second argument of `(:)', namely `l'
    In the expression: divisor : l
    In an equation for `u': u = divisor : l
Failed, modules loaded: none.
 
                        
Solution
I'm by no means a experienced Haskell programmer, but I would use the
following codethe imperative code below, but here's the direct transition from your code:Comparing non-ST vs ST:
non-ST variant
Your original function:
We exchange
100000by10000:221,476,488 bytes allocated in the heap 21,566,328 bytes copied during GC 172,813,068 bytes maximum residency (9 sample(s)) 4,434,480 bytes maximum slop 210 MB total memory in use (5 MB lost due to fragmentation) Tot time (elapsed) Avg pause Max pause Gen 0 378 colls, 0 par 0.41s 0.43s 0.0011s 0.0024s Gen 1 9 colls, 0 par 0.36s 0.37s 0.0409s 0.1723s INIT time 0.00s ( 0.00s elapsed) MUT time 0.28s ( 0.60s elapsed) GC time 0.77s ( 0.80s elapsed) EXIT time 0.00s ( 0.02s elapsed) Total time 1.05s ( 1.42s elapsed) %GC time 73.1% (56.4% elapsed) Alloc rate 787,471,957 bytes per MUT second Productivity 26.9% of total user, 19.8% of total elapsedEven though the program is pretty fast (1s after all), the memory footprint of 210MB is quite noticeable. Also, the memory needed grows squre, for a limit of 20000 you would need around 800 MB, for 20000 around 1.9GB.
ST variant
$ .\ST.exe +RTS -s > $null 300,728,400 bytes allocated in the heap 89,696,288 bytes copied during GC 13,628,272 bytes maximum residency (10 sample(s)) 292,972 bytes maximum slop 38 MB total memory in use (0 MB lost due to fragmentation) Tot time (elapsed) Avg pause Max pause Gen 0 565 colls, 0 par 0.14s 0.14s 0.0002s 0.0008s Gen 1 10 colls, 0 par 0.09s 0.08s 0.0076s 0.0223s INIT time 0.00s ( 0.00s elapsed) MUT time 0.11s ( 0.16s elapsed) GC time 0.23s ( 0.21s elapsed) EXIT time 0.00s ( 0.00s elapsed) Total time 0.34s ( 0.38s elapsed) %GC time 68.2% (56.6% elapsed) Alloc rate 2,749,516,800 bytes per MUT second Productivity 31.8% of total user, 28.9% of total elapsedOnly 38 MB, which is ~17% of your original implementation, but calculates 10 times more values (10000 vs 100000).
Imperative variant
If you throw
contentaway, you'll end up with the following program:$ .\Imperative.exe +RTS -s > $null 237,323,392 bytes allocated in the heap 63,304,856 bytes copied during GC 13,628,276 bytes maximum residency (7 sample(s)) 138,636 bytes maximum slop 34 MB total memory in use (0 MB lost due to fragmentation) Tot time (elapsed) Avg pause Max pause Gen 0 447 colls, 0 par 0.12s 0.09s 0.0002s 0.0008s Gen 1 7 colls, 0 par 0.05s 0.06s 0.0087s 0.0224s INIT time 0.00s ( 0.00s elapsed) MUT time 0.11s ( 0.18s elapsed) GC time 0.17s ( 0.16s elapsed) EXIT time 0.00s ( 0.00s elapsed) Total time 0.30s ( 0.34s elapsed) %GC time 57.9% (45.9% elapsed) Alloc rate 2,169,813,869 bytes per MUT second Productivity 42.1% of total user, 36.9% of total elapsedWhy doesn't it compile?
I a sense, you extract correctly (see above, that's almost the same code you used), but the inferred type for
updateis wrong, since your usage isn't correct. For exampleupdateshould work in theSTmonad, and therefore you would use it withmapM, notmap. Also, there are some other things off, for example you definedoUpdatebut never use it.