Haskell programmers seem to get by without variable mutation, which is odd if you’re used to C, Javascript, or another imperative language. It seems like you need them for loops, caches, and other state.

This article will go over many of the techniques I use for these purposes.

The Problem

Suppose you want to print an identity matrix:

1000000000
0100000000
0010000000
0001000000
0000100000
0000010000
0000001000
0000000100
0000000010
0000000001

In an imperative language like Perl, you’ll likely split this into 4 steps:

my $size = 10;
# 1. Declare the array, allocating memory if necessary.
my $array;

# 2. Initialize the array to 0
for (my $i = 0; $i < $size; $i++) {
for (my $j = 0; $j < $size; $j++) {
$array->[$i]->[$j] = 0;
}
}

# 3. Set the diagonal to 1.
for (my $idx = 0; $idx < $size; $idx++) {
$array->[$idx]->[$idx] = 1;
}

# 4. Print the array
for (my $i = 0; $i < $size; $i++) {
for (my $j = 0; $j < $size; $j++) {
my $value = $array->[$i]->[$j];
print $value;
}
print "\n";
}

In Haskell, we run into trouble when we realize there’s no built-in mutating assignment operation:

import Data.Array

writeArray = undefined

size = 10
-- 1. Declare the array
arr :: Array (Int,Int) Integer
arr = array ((1,1), (size,size)) []

main :: IO ()
main = do
-- 2. Initialize the array to 0
sequence_ $ do
i <- [1..size]
j <- [1..size]
return $ writeArray arr i j 0
-- 3. Set the diagonal to 1
sequence_ $ do
i <- [1..size]
return $ writeArray arr i i 1

-- 4. Print the array
sequence_ $ do
i <- [1..size]
j <- [1..size]
return $ do
putChar $ if arr ! (i,j) == 0
then '0'
else '1'
if j == size
then putChar '\n'
else return ()

The expression writeArray array i j k is supposed to be equivalent to Perl’s

$array->[$i]->[$j] = $k;

Each call to sequence_ executes a list of actions. The do notation in List context is a way of stretching the list generator notation out to multiple lines: Step 2 above is equivalent to

sequence_ [ writeArray arr i j 0 | i <- [1..10], j <- [1..10] ]

Notice that the indices are one-based([1..10]) and not zero-based([0..9]), because I declared the boundaries of the array to be (1,1) and (10,10) here:

arr = array ((1,1), (size,size)) []

The code above compiles but fails to run because writeArray is not defined. There’s no way to define writeArray, because values are immutable in Haskell. We use writeArray in 2 places: When we initialize the array, and when we modify the diagonal. We can initialize the array when we declare it, so that removes the first issue:

import Data.Array

-- 1. Declare the array
size = 10
arr :: Array (Int,Int) Integer
arr = array ((1,1), (size,size)) $ do
-- 2. Initialize the array to 0
i <- [1..size]
j <- [1..size]
return ((i,j), 0)

printElement :: (Int, Int) -> IO ()
printElement (i,j) = do
putChar $ if arr ! (i,j) == 0
then '0'
else '1'
if j == size
then putChar '\n'
else return ()

main :: IO ()
main = do
-- 4. Print the array
sequence_ $ do
i <- [1..size]
j <- [1..size]
return $ printElement (i,j)

In arr and main, the do is in List context because sequence_ and array expect a List. In printElement, the do is in IO context, which allows us to define an action that chains together multiple dependent actions.

If you run this sample, you should see a 10x10 matrix with all zeroes:

0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000

Steps 1,2, and 4 are easy. Our problem is Step 3: We have no way to define writeArray, since the array is immutable.

Mutable Arrays

If arrays are immutable, could we somehow make it mutable?

For arrays specifically, there is a mutable variant IOArray that lets you allocate, read, and write mutable arrays in IO context. It even has a writeArray function that is very similar to the one we need. Here’s how you would use it:

import Data.Array.MArray
import Data.Array.IO

size = 10

(!) = readArray

main :: IO ()
main = do
-- 1. Declare the array
arr <- newArray ((1,1), (size,size)) undefined
let _ = arr :: IOArray (Int,Int) Integer

-- 2. Initialize the array to 0
sequence_ $ do
i <- [1..size]
j <- [1..size]
return $ writeArray arr (i, j) 0

-- 3. Set the diagonal to 1
sequence_ $ do
i <- [1..size]
return $ writeArray arr (i, i) 1

-- 4. Print the array
sequence_ $ do
i <- [1..size]
j <- [1..size]
return $ do
x <- arr ! (i,j)

putChar $ if x == 0
then '0'
else '1'
if j == size
then putChar '\n'
else return ()

The line let _ = arr :: IOArray (Int,Int) Integer is a way of giving a type signature to arr in do notation.

People have written immutable, mutable, contiguous, automatically-parallelized, GPU-accelerated, and other types of arrays. It’s always a good idea to choose the right data structure when writing a program in any language.

In general, not every useful data structure automatically comes with a mutable variant. You can always write one yourself, but for the rest of this article we’ll assume the array is immutable so that you can use the techniques here more generally.

Mutable References

Data.IORef allows us to create and modify mutable garbage-collected values in IO context. This makes programming in IO context similar to a language like C# or Javascript, if a bit more verbose.

References are normally transparent in imperative languages: C++ has the concept of an lvalue and rvalue because reads and writes are not explicitly written out. Also, stack-allocated declarations look similar to assignments, so it’s not clear that allocation is happening:

#include <stdio.h>

int main() {
int x = 5; // Allocation
x = 10; // Write
int y = x; // Read

printf("%d\n", y);
}

Pointers make the allocation and indirect access a little more explicit. However, *x can involve either a read or write depending on the context:

#include <stdio.h>

int main() {
int *x = new int(5); // Allocation
*x = 10; // Write
int y = *x; // Read

printf("%d\n", y);
}

Using IORef, that is equivalent to:

import Data.IORef

main = do
-- x :: IORef Int
x <- newIORef 5
writeIORef x 10
-- y :: Int
y <- readIORef x

putStrLn $ show y

Like pointers, a reference to an integer is different from an integer. I’ve left commented-out type signatures above to show this.

  • newIORef creates a mutable reference.
  • readIORef reads the reference
  • writeIORef changes it to point to a new value. (Old values are automatically garbage-collected.)

All of these need to be in IO context, because reads and writes can’t be reordered.

modifyIORef' will read the value using a reference, apply a function to it, and write the value back. You almost always want to use the strict version modifyIORef' rather than the lazy version modifyIORef, because it has more predictable performance.

import Data.Array
import Data.IORef

type IntArray = Array (Int,Int) Integer

writeArray :: IORef IntArray -> (Int, Int) -> Integer -> IO ()
writeArray ref index value =
modifyIORef' ref $ \arr ->
arr // [(index, value)]

size = 10

printElement :: IORef IntArray -> (Int, Int) -> IO ()
printElement ref (i,j) = do
arr <- readIORef ref
putChar $ if arr ! (i,j) == 0
then '0'
else '1'
if j == size
then putChar '\n'
else return ()


main :: IO ()
main = do
-- 1. Declare the array
-- ref :: IORef IntArray
ref <- newIORef $ array ((1,1), (size,size)) $ do
-- 2. Initialize the array to 0
i <- [1..size]
j <- [1..size]
return ((i,j), 0)

-- 3. Set the diagonal to 1
sequence_ $ do
i <- [1..size]
return $ writeArray ref (i,i) 1
-- 4. Print the array
sequence_ $ do
i <- [1..size]
j <- [1..size]
return $ printElement ref (i,j)

The expression arr // [(index, value)] is a new array with an updated value at index. It does not read or write to memory - modifyIORef will do the reading and writing - it simply is the new array. [(index, value)] is a list because (//) allows us to update multiple values at once, though I only update a single value each time here.

One shortcoming of this technique is that it requires your code to be in IO context: main, printElement, and writeArray all use IO. If you want to add mutable references throughout your code, you’ll have to change everything to be in IO context. If you want to actually mutate global variables, this is necessary; but see the ST monad below to avoid this if you only need mutation within a single function.

While this is the most direct way to do what we want, Haskellers usually get by without any mutation. How would that work?

The Haskell Way

We know how to create new arrays, but updating them is tricky. So one way around this is to create a new array, and have later code use it. This is what I would likely write in real code:

import Data.Array

type IntArray = Array (Int,Int) Integer

size = 10
-- 1. Declare the array
arr :: IntArray
arr = array ((1,1), (size,size)) $ do
-- 2. Initialize the array to 0
i <- [1..size]
j <- [1..size]
return ((i,j), 0)

-- 3. Modify the array
updateArray :: IntArray -> IntArray
updateArray arr = arr // [((i,i), 1) | i <- [1..size] ]

printElement :: IntArray -> (Int, Int) -> IO ()
printElement arr (i,j) = do
putChar $ if arr ! (i,j) == 0
then '0'
else '1'
if j == size
then putChar '\n'
else return ()

main = do
let arr2 :: IntArray
arr2 = updateArray arr
-- 4. Print the array
sequence_ $ do
i <- [1..size]
j <- [1..size]
return $ printElement arr2 (i, j)

Recall that (//) takes a list of updates, so we can calculate all the places that we need to update and pass them in all at once. arr is the original zero matrix, while arr2 is the updated identity matrix. We make sure that printElement takes the array as a parameter, and call it with arr2.

In subsequent sections, we’ll mostly show various ways to implement updateArray using mutation-like constructs. You can assume the rest of the code remains the same.

Shadowing

In Haskell, later variables with the same name “shadow” earlier variables, so we can pretend that we’ve updated the array in-place by giving the new array the same name:

updateArray :: IntArray -> IntArray
updateArray arr =
case arr // [((1,1), 1)] of
arr -> case arr // [((2,2), 1)] of
arr -> case arr // [((3,3), 1)] of
arr -> case arr // [((4,4), 1)] of
arr -> case arr // [((5,5), 1)] of
arr -> case arr // [((6,6), 1)] of
arr -> case arr // [((7,7), 1)] of
arr -> case arr // [((8,8), 1)] of
arr -> case arr // [((9,9), 1)] of
arr -> case arr // [((10,10), 1)] of
arr -> arr

updateArray in the above code is supposed to be similar to this Perl:

# 1. Declare the array
my $array;
# 3. Modify the array
$array->[0]->[0] = 1;
$array->[1]->[1] = 1;
$array->[2]->[2] = 1;
$array->[3]->[3] = 1;
$array->[4]->[4] = 1;
$array->[5]->[5] = 1;
$array->[6]->[6] = 1;
$array->[7]->[7] = 1;
$array->[8]->[8] = 1;
$array->[9]->[9] = 1;

Another way of writing it in Haskell that avoids the deep nesting is:

import Control.Monad.Identity

updateArray :: IntArray -> IntArray
updateArray arr = runIdentity $ do
arr <- return $ arr // [((1,1), 1)]
arr <- return $ arr // [((2,2), 1)]
arr <- return $ arr // [((3,3), 1)]
arr <- return $ arr // [((4,4), 1)]
arr <- return $ arr // [((5,5), 1)]
arr <- return $ arr // [((6,6), 1)]
arr <- return $ arr // [((7,7), 1)]
arr <- return $ arr // [((8,8), 1)]
arr <- return $ arr // [((9,9), 1)]
arr <- return $ arr // [((10,10), 1)]
return arr

or

setDiagonal :: Int -> IntArray -> IntArray
setDiagonal i arr = arr // [((i,i), 1)]

updateArray :: IntArray -> IntArray
updateArray arr = runIdentity $ do
arr <- return $ setDiagonal 1 arr
arr <- return $ setDiagonal 2 arr
arr <- return $ setDiagonal 3 arr
arr <- return $ setDiagonal 4 arr
arr <- return $ setDiagonal 5 arr
arr <- return $ setDiagonal 6 arr
arr <- return $ setDiagonal 7 arr
arr <- return $ setDiagonal 8 arr
arr <- return $ setDiagonal 9 arr
arr <- return $ setDiagonal 10 arr
return arr

The do notation above is in Identity context, which is a way of chaining together ordinary Haskell values and functions using the do syntax. The compiler should convert it to deeply-nested case statements, or something equivalent.

I needed to use do notation or case because pattern matches are non-recursive. This will produce an infinite loop, because let bindings are recursive by default; so arr will be defined in terms of itself, not the previous array.

updateArray :: IntArray -> IntArray
updateArray arr =
let arr = arr // [((1,1), 1)]
in arr

There are two shortcomings with using shadowing as a replacement for mutation:

  • It’s a little verbose to repeat the variable name twice when “reading” and “writing”.
  • It works when there are a fixed number of updates(10), but not in e.g. a while loop that repeatedly updates a variable.

Function Composition

Look back at this example from Shadowing:

import Control.Monad.Identity

updateArray :: IntArray -> IntArray
updateArray arr = runIdentity $ do
arr <- return $ setDiagonal 1 arr
arr <- return $ setDiagonal 2 arr
arr <- return $ setDiagonal 3 arr
arr <- return $ setDiagonal 4 arr
arr <- return $ setDiagonal 5 arr
arr <- return $ setDiagonal 6 arr
arr <- return $ setDiagonal 7 arr
arr <- return $ setDiagonal 8 arr
arr <- return $ setDiagonal 9 arr
arr <- return $ setDiagonal 10 arr
return arr

Since we don’t really do anything with the variable other than feed it into the next binding, some Haskellers would remove the variables and compose the functions together:

updateArray :: IntArray -> IntArray
updateArray =
setDiagonal 10 .
setDiagonal 9 .
setDiagonal 8 .
setDiagonal 7 .
setDiagonal 6 .
setDiagonal 5 .
setDiagonal 4 .
setDiagonal 3 .
setDiagonal 2 .
setDiagonal 1

Or

updateArray :: IntArray -> IntArray
updateArray arr =
setDiagonal 10 $
setDiagonal 9 $
setDiagonal 8 $
setDiagonal 7 $
setDiagonal 6 $
setDiagonal 5 $
setDiagonal 4 $
setDiagonal 3 $
setDiagonal 2 $
setDiagonal 1 $
arr

Or

updateArray :: IntArray -> IntArray
updateArray arr = foldr ($) arr [ setDiagonal i | i <- [1..10] ]

Or(from Data.Monoid):

import Data.Monoid

composeAll :: [a -> a] -> (a -> a)
composeAll fs = appEndo $ mconcat $ map Endo fs

updateArray :: IntArray -> IntArray
updateArray = composeAll [ setDiagonal i | i <- [1..10] ]

This works okay when we have a chain updating 1 variable. You can extend it to more by using a tuple:

updateArray :: IntArray -> IntArray
updateArray arr =
getArray $
updateNext $ -- (1,1)
updateNext $ -- (2,2)
updateNext $ -- (3,3)
updateNext $ -- (4,4)
updateNext $ -- (5,5)
updateNext $ -- (6,6)
updateNext $ -- (7,7)
updateNext $ -- (8,8)
updateNext $ -- (9,9)
updateNext $ -- (10,10)
(arr, 1, 1)
where
updateNext :: (IntArray, Int, Int) -> (IntArray, Int, Int)
updateNext = \(arr, i, j) -> (arr // [((i,j), 1)], i+1, j+1)

getArray :: (IntArray, Int, Int) -> IntArray
getArray (arr, _, _) = arr

Here, we make the indices i and j part of the state, and increment them after each application of updateNext. At the end, we would get the new array, i, and j; but we only care about the array, so getArray discards i and j.

One shortcoming of this technique is that you have to ensure that the state parameter is always last, to compose the functions. See the State monad for a way of hiding the state paremeter.

Tail-recursion

The most powerful way to write high-performance Haskell is to make your state explicit, and call an inner function written in tail-recursive style. I’ll add the variables to the previous example:

type LoopState = (Int, Int)

updateArray :: IntArray -> IntArray
updateArray arr =
let (st1, arr1) = updateNext (initialState, arr)
(st2, arr2) = updateNext (st1, arr1)
(st3, arr3) = updateNext (st2, arr2)
(st4, arr4) = updateNext (st3, arr3)
(st5, arr5) = updateNext (st4, arr4)
(st6, arr6) = updateNext (st5, arr5)
(st7, arr7) = updateNext (st6, arr6)
(st8, arr8) = updateNext (st7, arr7)
(st9, arr9) = updateNext (st8, arr8)
(st10, arr10) = updateNext (st9, arr9)
in arr10
where
initialState :: LoopState
initialState = (1,1)

updateNext :: (LoopState, IntArray) -> (LoopState, IntArray)
updateNext = \((i, j), arr) -> ((i+1, j+1), arr // [((i,j), 1)])

One problem with this is that we’re duplicating the same statement 10 times. It’s a little silly to have 18 lines when the Perl is only 3:

for (my $idx = 0; $idx < $size; $idx++) {
$array->[$idx]->[$idx] = 1;
}

The for loop consists of 4 components:

  • An initial state: my $idx = 0, and $array
  • A condition that reads the state: $idx < $size
  • Statements advancing the state: $idx++, and $array->[$idx]->[$idx] = 1;
  • (Optional) A way to convert the state into a return value: $array after the loop.

Here is the most general way to convert a loop into a tail-recursive loop function:

type LoopState = Int

updateArray :: IntArray -> IntArray
updateArray arr = loop initialState arr
where
initialState :: LoopState
initialState = 1

loop :: LoopState -> IntArray -> IntArray
loop i arr =
if shouldLoop
then loop nextState nextArr
else finalize i arr
where
shouldLoop = i <= 10
nextState = i+1
nextArr = setDiagonal i arr

finalize :: LoopState -> IntArray -> IntArray
finalize i arr = arr

With this, it’s mechanical to replace any simple for loops with a tail-recursive function. The only loops that can’t be converted are those that do something other than pure computation, such as write to a logfile or connect to a database. For those, see Recursive IO below.

Here’s a shorter function that I would be more likely to use in real code:

updateArray :: IntArray -> IntArray
updateArray arr = loop 1 arr
where
loop i arr = if i <= 10
then loop (i+1) (setDiagonal i arr)
else arr

State Monad

In the Function Composition section, we saw that we can emulate mutable variables by making the state the last parameter in a sequence of functions and composing them.

The State monad is a way of automatically threading this state parameter to all code that uses it. I’ll add the variables back to the previous example:

type LoopState = Int

updateArray :: IntArray -> IntArray
updateArray arr =
let (st1, arr1) = updateNext (initialState, arr)
(st2, arr2) = updateNext (st1, arr1)
(st3, arr3) = updateNext (st2, arr2)
(st4, arr4) = updateNext (st3, arr3)
(st5, arr5) = updateNext (st4, arr4)
(st6, arr6) = updateNext (st5, arr5)
(st7, arr7) = updateNext (st6, arr6)
(st8, arr8) = updateNext (st7, arr7)
(st9, arr9) = updateNext (st8, arr8)
(st10, arr10) = updateNext (st9, arr9)
in arr10
where
initialState :: LoopState
initialState = 1

updateNext :: (LoopState, IntArray) -> (LoopState, IntArray)
updateNext = \(i, arr) -> (i+1, setDiagonal i arr)

A value of type State s a is a value of type a that implicitly reads and writes a state parameter of type s. You almost always want the strict version. Here is the above code written to use State:

import Control.Monad.State.Strict
type LoopState = (Int, IntArray)

updateArray :: IntArray -> IntArray
updateArray arr = evalState action initialState
where
action :: State LoopState IntArray
action = do
updateNext -- 1
updateNext -- 2
updateNext -- 3
updateNext -- 4
updateNext -- 5
updateNext -- 6
updateNext -- 7
updateNext -- 8
updateNext -- 9
updateNext -- 10

initialState :: LoopState
initialState = (1, arr)

updateNext :: State LoopState IntArray
updateNext = do
(i, arr) <- get
let arr' = setDiagonal i arr
put (i+1, arr')
return arr'

The pattern match (i, arr) <- get pattern matches the state that is implicitly being passed around. put (i+1, arr') passes a new state to the next statement.

Take a moment to see how the tuple is being passed along. It’s entirely equivalent to the let binding example.

This can also be written much shorter:

updateArray :: IntArray -> IntArray
updateArray arr = evalState (replicateM 10 updateNext) (1, arr)
where
updateNext :: State LoopState IntArray
updateNext = state $ \(i, arr) -> (arr', (i+1, arr'))
where arr' = setDiagonal i arr

You can also mix tail recursive style to emulate loops that change mutable variables:

updateArray :: IntArray -> IntArray
updateArray arr = evalState loop (1, arr)
where
loop :: State LoopState IntArray
loop = do
(i, arr) <- get
if i <= 10
then do
put (i+1, setDiagonal i arr)
loop
else return arr

ST Monad

The IORef example had the disadvantage that it required code to be in IO context to read or write references. Code in ST context also allows mutable variables, and can be embedded anywhere. Instead of an IORef, you’ll want an STRef.

import Control.Monad.ST
import Data.STRef

updateArray :: IntArray -> IntArray
updateArray arr = runST $ do
iRef <- newSTRef 1
arrRef <- newSTRef arr
let loop = do
i <- readSTRef iRef
arr <- readSTRef arrRef
if i <= 10
then do
writeSTRef iRef (i+1)
writeSTRef arrRef (setDiagonal i arr)
loop
else return arr
loop

The difference between ST and IO is that ST only allows references to be local to the runST call. Mutable local variables have no external side effects. They are completely safe to use, which is why we can embed them anywhere. The compiler ensures that the references never escape.

IO lets you use global variables, write files, connect to databases, and all sorts of other things.

Implicit Parameters

In Shadowing, we learned that you can sometimes simulate mutation by creating a new value with the same name, rather than updating the existing value. In State Monad, we learned that you can make this more composable by hiding the parameters and implicitly threading them along. ImplicitParams is a language extension that allows you to thread hidden parameters anywhere in your code.

Their chief advantage is that you can add them to any existing implementation code and it will still compile. You may need to update type signatures, but in well-structured code you’ll often only have a single program-specific type alias to change.

I’ve used them for global configuration before: Imagine connecting to a database on program startup to retrieve configuration information, which is accessible throughout most of the program. You usually wouldn’t use them for inner loops or computation, so I’ve changed the example for this section.

Suppose we’ve unwisely chosen a List as the data structure for our program, and it uses too many List-specific utility functions to easily change to a different data type. Eventually, we plan to switch to an array or Data.Vector, but in the meantime there are performance issues because we use length everywhere(which is for linked lists). You can use Implicit Parameters to calculate the length of a list once, and pass it around.

Here’s the current code:

import Control.Monad.State

type ProgramMonad a = State [Double] a

-- We want engagement to be higher for customers with fewer sales
-- so they buy the 10+ package.
importantCalculation :: ProgramMonad Double
importantCalculation = do
arr <- get
if length arr > 10
then return $ sum $ map log arr
else return $ product $ map exp arr

main = putStrLn $ show $ evalState importantCalculation [5,8,9,3,3,1]

The length arr > 10 is slowing down the calculation. Add the implicit parameter:

type ProgramMonad a = (?length :: Int) => State [Double] a

main =
let ?length = length arr
in putStrLn $ show $ evalState importantCalculation arr
where
arr = [5,8,9,3,3,1]

The important thing above is that we added the implicit parameter without needing to change any of the code in the calculation: We only changed the type signature, and set ?length once near the beginning of our program. Of course, now that it’s available, we can temporarily fix the performance problem by using it:

importantCalculation :: ProgramMonad Double
importantCalculation = do
arr <- get
if ?length > 10
then return $ sum $ map log arr
else return $ product $ map exp arr

Recursive IO

You can mix recursive style with IORef to write code that feels like C in Haskell. Here’s the plain version:

import Control.Monad
import Data.Array
import Data.IORef

type IntArray = Array (Int,Int) Integer

setDiagonal :: Int -> IntArray -> IntArray
setDiagonal i arr = arr // [((i,i), 1)]

updateArray :: IORef IntArray -> IO ()
updateArray arrRef = do
iRef <- newIORef 1
let loop = do
i <- readIORef iRef
arr <- readIORef arrRef
if i <= 10
then do
writeIORef iRef (i+1)
writeIORef arrRef (setDiagonal i arr)
loop
else return ()
loop

size = 10

printElement :: IORef IntArray -> (Int, Int) -> IO ()
printElement ref (i,j) = do
arr <- readIORef ref
putChar $ if arr ! (i,j) == 0
then '0'
else '1'
if j == size
then putChar '\n'
else return ()

main :: IO ()
main = do
-- 1. Declare the array
arrRef <- newIORef $ array ((1,1), (size,size)) $ do
-- 2. Initialize the array to 0
i <- [1..size]
j <- [1..size]
return ((i,j), 0)

-- 3. Set the diagonal to 1
updateArray arrRef

-- 4. Print the array
sequence_ $ do
i <- [1..size]
j <- [1..size]
return $ printElement arrRef (i,j)

And here’s a few variations with some helper functions:

Use when instead of having an empty else clause:

updateArray :: IORef IntArray -> IO ()
updateArray arrRef = do
iRef <- newIORef 1
let loop = do
i <- readIORef iRef
arr <- readIORef arrRef
when (i <= 10) $ do
writeIORef iRef (i+1)
writeIORef arrRef (setDiagonal i arr)
loop
loop

Use replicateM_ to repeat the action rather than hand-coding a recursive loop:

updateArray :: IORef IntArray -> IO ()
updateArray arrRef = do
iRef <- newIORef 1
replicateM_ 10 $ do
i <- readIORef iRef
arr <- readIORef arrRef
writeIORef iRef (i+1)
writeIORef arrRef (setDiagonal i arr)

Use modifyIORef' rather than reading and writing:

updateArray :: IORef IntArray -> IO ()
updateArray arrRef = do
iRef <- newIORef 1
replicateM_ 10 $ do
i <- readIORef iRef
writeIORef iRef (i+1)
modifyIORef' arrRef (setDiagonal i)

All-in-all, it’s not the most elegant, but it gets the job done.

Conclusion

You’ve seen a lot of different ways to do the same thing. Hopefully some of them are new, and help you port your own imperative thinking and code over to Haskell. Send me a message on Twitter if this helped at all.

The best one-liner to create the array is probably this:

identity :: IntArray
identity = array ((1,1),(10,10)) [ ((i,j), if i == j then 1 else 0) | i <- [1..size], j <- [1..size] ]
  • Thanks to Tome Jaguar for the concept and code used in the Mutable Arrays section!