Usando programação dinâmica em Haskell? [Aviso: solução do ProjectEuler 31 dentro]

Na resolução do problema do projecteuler.net # 31 [SPOILERS À FRENTE] (contando o número de maneiras de ganhar 2 £ com as moedas britânicas), eu queria usar programação dinâmica. Comecei com o OCaml e escrevi a seguinte programação muito eficiente:

open Num

let make_dyn_table amount coins =
  let t = Array.make_matrix (Array.length coins) (amount+1) (Int 1) in
  for i = 1 to (Array.length t) - 1 do
    for j = 0 to amount do
      if j < coins.(i) then
        t.(i).(j) <- t.(i-1).(j)
      else
        t.(i).(j) <- t.(i-1).(j) +/ t.(i).(j - coins.(i))
    done
  done;
  t

let _ =
  let t = make_dyn_table 200 [|1;2;5;10;20;50;100;200|] in
  let last_row = Array.length t - 1 in
  let last_col = Array.length t.(last_row) - 1 in
  Printf.printf "%s\n" (string_of_num (t.(last_row).(last_col)))

Isso é executado em ~ 8ms no meu laptop. Se eu aumentar o valor de 200 pence para um milhão, o programa ainda encontra uma resposta em menos de dois segundos.

Eu traduzi o programa para Haskell (que definitivamente não era divertido em si), e embora termine com a resposta certa para 200 pence, se eu aumentar esse número para 10000, meu laptop parará bruscamente (muita surra). Aqui está o código:

import Data.Array

createDynTable :: Int -> Array Int Int -> Array (Int, Int) Int
createDynTable amount coins =
    let numCoins = (snd . bounds) coins
        t = array ((0, 0), (numCoins, amount))
            [((i, j), 1) | i <- [0 .. numCoins], j <- [0 .. amount]]
    in t

populateDynTable :: Array (Int, Int) Int -> Array Int Int -> Array (Int, Int) Int
populateDynTable t coins =
    go t 1 0
        where go t i j
                 | i > maxX = t
                 | j > maxY = go t (i+1) 0
                 | j < coins ! i = go (t // [((i, j), t ! (i-1, j))]) i (j+1)
                 | otherwise = go (t // [((i, j), t!(i-1,j) + t!(i, j - coins!i))]) i (j+1)
              ((_, _), (maxX, maxY)) = bounds t

changeCombinations amount coins =
    let coinsArray = listArray (0, length coins - 1) coins
        dynTable = createDynTable amount coinsArray
        dynTable' = populateDynTable dynTable coinsArray
        ((_, _), (i, j)) = bounds dynTable
    in
      dynTable' ! (i, j)

main =
    print $ changeCombinations 200 [1,2,5,10,20,50,100,200]

Eu adoraria ouvir de alguém que conhece Haskell bem porque o desempenho desta solução é tão ruim.

questionAnswers(2)

yourAnswerToTheQuestion