Basically, I'm comparably new to Haskell, which means lacking both knowledge of advanced principles, available libraries and, maybe, some syntax.
For educational purposes I hacked together a code implementing Stam-style fluid dynamics solver. For now the code is running in constant space and linear time, which is exactly what's needed. But feeling remains that code I wrote is rather ugly.
I have serious concerns about whether this code is possible to read at all, but hope that somebody could point out most obvious idiosyncrasies.
import Data.Array.Unboxed import Data.List import Text.Printf n = 20 timeStep = 0.1 numSteps = 100 indicies = [[0..n + 1], [1..n], [2..n-1]] type Matrix = UArray (Int, Int) Float data Bound = XHard | YHard | Soft data State = State !Matrix !Matrix !Matrix applyNTimes :: Int -> (a -> a) -> a -> a applyNTimes times f val = foldl' (\m i -> f m) val [1..times] showMatrix :: Matrix -> String showMatrix m = foldl (++) "" [showRow m i ++ "\n" | i <- head indicies] where showRow m i = foldl (++) "" [printf "%.3f " (m ! (i, j)) | j <- head indicies] showState :: State -> String showState (State u v d) = showMatrix d createMatrix :: [Int] -> ((Int, Int) -> Float) -> Matrix createMatrix ind f = array ((0, 0), (n + 1, n + 1)) [((i, j), f (i, j)) | j <- ind, i <- ind] // [(ix, 0) | ix <- other] where other = [(i, j) | i <- head indicies, j <- head indicies] \\ [(i, j) | i <- ind, j <- ind] zeroMatrix :: Matrix zeroMatrix = createMatrix (head indicies) (\ (i, j) -> 0.0) add :: Matrix -> Matrix -> Matrix add a b = createMatrix (head indicies) (add_cell a b) where add_cell a b ix = a ! ix + b ! ix addPecularSource :: State -> State addPecularSource (State u v d) = State u v (d `add` createMatrix (head indicies) spike) where spike ix = if ix == (n `div` 2, n `div` 2) then 0.1 else 0.0 advance :: State -> State advance s = solveDensity . addPecularSource . solveVelocity $ s main :: IO() main = putStrLn . showState . applyNTimes numSteps advance $ State z z z where z = zeroMatrix solveDensity :: State -> State solveDensity (State u v d) = State u v d' where d' = advect Soft (diffuse d diffusion) u v where diffusion = 0.0 :: Float setCorners :: Matrix -> Matrix setCorners a = a // [((n + 1, n + 1), (a ! (n , n + 1) + a ! (n + 1, n)) / 2), ((n + 1, 0), (a ! (n , 0) + a ! (n + 1, 1)) / 2), ((0, n + 1), (a ! (0, n) + a ! (1, n + 1)) / 2), ((0, 0), (a ! (1 , 0) + a ! (0, 1)) / 2)] diffuse :: Matrix -> Float -> Matrix diffuse c0 diff = linearSolver c0 a (1 + 4 * a) where a = timeStep * diff * fromIntegral n * fromIntegral n setBoundary :: Bound -> Matrix -> Matrix setBoundary b m = setCorners (setSides b m) setSides :: Bound -> Matrix -> Matrix setSides XHard m = setSides Soft m // ([((0, i), - m ! (0, i)) | i <- indicies !! 1] ++ [((n + 1, i), - m ! (n + 1, i)) | i <- indicies !! 1]) setSides YHard m = setSides Soft m // ([((i, 0), - m ! (i, 0)) | i <- indicies !! 1] ++ [((i, n + 1), - m ! (i, n + 1)) | i <- indicies !! 1]) setSides Soft m = m // ([((0, i), m ! (1, i)) | i <- indicies !! 1] ++ [((n + 1, i), m ! (n, i)) | i <- indicies !! 1] ++ [((i, 0), m ! (i, 1)) | i <- indicies !! 1] ++ [((i, n + 1), m ! (i, n)) | i <- indicies !! 1]) linearSolver :: Matrix -> Float -> Float -> Matrix linearSolver x0 a c = applyNTimes 20 (iterFun x0 a c) zeroMatrix where iterFun x0 a c m = setBoundary Soft (createMatrix (indicies !! 1) (update m x0 a c) ) where update m x0 a c (i, j) = (a * (m ! (i - 1, j) + m ! (i + 1, j) + m ! (i, j - 1) + m ! (i, j + 1)) + x0 ! (i, j)) / c curl :: Matrix -> Matrix -> Matrix curl u v = createMatrix (indicies !! 1) gen where gen (i, j) = (u ! (i, j + 1) - u ! (i, j - 1) - v ! (i + 1, j) + v ! (i - 1, j)) / 2 archCell :: Matrix -> Float -> Float -> (Int, Int) -> Float archCell d gravity push (i, j) = gravity * d ! (i, j) + push * (d ! (i, j) - ambient d) where ambient d = sum [d ! (i, j) | i <- indicies !! 1, j <- indicies !! 1] archimedes :: Matrix -> Matrix archimedes d = createMatrix (head indicies) (archCell d gravity push) where (gravity, push) = (0.001 :: Float, 0.005 :: Float) solveVelocity :: State -> State solveVelocity (State u v d) = State u' v' d where (u', v') = project (advect YHard uProjected uProjected vProjected) (advect XHard vProjected uProjected vProjected) where (uProjected, vProjected) = project uDiffused vDiffused where (uDiffused, vDiffused) = (diffuse uVorticityConfined viscosity, diffuse (vVorticityConfined `add` archimedes d) viscosity) where ((uVorticityConfined, vVorticityConfined), viscosity) = (vorticityConfimentForce u v, 0.0) advectCell :: Matrix -> Matrix -> Matrix -> (Int, Int) -> Float advectCell d0 du dv (i, j) = s0 * t0 * d0 ! (floor x, floor y) + s0 * t1 * d0 ! (floor x, floor y + 1) + s1 * t0 * d0 ! (floor x + 1, floor y) + s1 * t1 * d0 ! (floor x + 1, floor y + 1) where (s0, t0, s1, t1, x, y) = (1.0 - x' + xx, 1.0 - y' + yy, x' - xx, y' - yy, x', y') where (x', y', xx, yy) = (x, y, fromIntegral (floor x), fromIntegral (floor y)) where (x, y) = (sort [0.5 + fromIntegral n, 0.5, fromIntegral i - dt0 * du ! (i, j)] !! 1, sort [0.5 + fromIntegral n, 0.5, fromIntegral j - dt0 * dv ! (i, j)] !! 1) where dt0 = timeStep * fromIntegral n advect :: Bound -> Matrix -> Matrix -> Matrix -> Matrix advect b d0 du dv = setBoundary b (createMatrix (indicies !! 1) (advectCell d0 du dv)) projectCellX :: Matrix -> Matrix -> (Int, Int) -> Float projectCellX x p (i, j) = x ! (i, j) - 0.5 * fromIntegral n * (p!(i + 1, j) - p!(i - 1, j)) projectCellY :: Matrix -> Matrix -> (Int, Int) -> Float projectCellY y p (i, j) = y ! (i, j) - 0.5 * fromIntegral n * (p!(i, j + 1) - p!(i, j - 1)) project :: Matrix -> Matrix -> (Matrix, Matrix) project x y = (setBoundary YHard x', setBoundary XHard y') where (x', y') = (createMatrix (indicies !! 1) (projectCellX x p), createMatrix (indicies !! 1) (projectCellY y p)) where p = linearSolver (setBoundary Soft diverg) 1 4 where diverg = createMatrix (indicies !! 1) (computeDiv x y) where computeDiv x y (i, j) = -0.5 * (x ! (i + 1, j) - x ! (i - 1, j) + y ! (i, j + 1) - y ! (i, j - 1)) / fromIntegral n dwdx :: Matrix -> (Int, Int) -> Float dwdx c (i, j) = (abs (c ! (i + 1, j)) - abs (c ! (i - 1, j))) / 2 dwdy :: Matrix -> (Int, Int) -> Float dwdy c (i, j) = (abs (c ! (i, j + 1)) - abs (c ! (i, j - 1))) / 2 len :: Matrix -> (Int, Int) -> Float len c (i, j) = sqrt(dwdx c (i, j) * dwdx c (i, j) + dwdy c (i, j) * dwdy c (i, j)) + eps where eps = 1e-6 funX :: Matrix -> (Int, Int) -> Float funX c (i, j) = - c ! (i, j) * dwdy c (i, j) / len c (i, j) funY :: Matrix -> (Int, Int) -> Float funY c (i, j) = c ! (i, j) * dwdx c (i, j) / len c (i, j) vorticityConfimentForce :: Matrix -> Matrix -> (Matrix, Matrix) vorticityConfimentForce u v = (createMatrix (indicies !! 2) (funX (curl u v)), createMatrix (indicies !! 2) (funY (curl u v))) GHC 6.12.3 was used. Bang-patterns were added via -XBangPatterns.
My question is: how to change this code to be more comprehensible and idiomatic?