Pointers in Haskell

Haskell supports references, allowing you to pass mutable values within your program. However, the concept is quite different from pointers in imperative languages.

import Data.IORef

-- We'll show how mutable references work with 2 functions:
-- 'zeroVal' and 'zeroRef'. 'zeroVal' takes an Int, so it
-- receives a copy of the value.
zeroVal :: Int -> IO ()
zeroVal val = do
    putStrLn $ "Inside zeroVal: " ++ show val
    -- This modification is local and won't affect the original value

-- 'zeroRef' takes an IORef Int, which is a mutable reference to an Int.
-- It can modify the value at the referenced location.
zeroRef :: IORef Int -> IO ()
zeroRef ref = do
    writeIORef ref 0
    val <- readIORef ref
    putStrLn $ "Inside zeroRef: " ++ show val

main :: IO ()
main = do
    -- Create a new mutable reference
    i <- newIORef 1
    putStr "initial: "
    readIORef i >>= print

    -- Pass the value to zeroVal
    readIORef i >>= zeroVal
    putStr "after zeroVal: "
    readIORef i >>= print

    -- Pass the reference to zeroRef
    zeroRef i
    putStr "after zeroRef: "
    readIORef i >>= print

    -- We can print the "address" of the reference, but it's not as
    -- straightforward as in languages with explicit pointers
    putStr "reference: "
    print i

In this Haskell version, we use IORef to create mutable references, which is the closest equivalent to pointers in a pure functional language like Haskell.

zeroVal doesn’t change the value in main, but zeroRef does because it has a reference to the mutable location.

When you run this program, you’ll see output similar to:

$ runhaskell pointers.hs
initial: 1
Inside zeroVal: 1
after zeroVal: 1
Inside zeroRef: 0
after zeroRef: 0
reference: IORef (0x...)

Note that Haskell’s approach to mutability is quite different from imperative languages. In Haskell, we explicitly use IORef to create mutable references, and all operations on these references are performed within the IO monad to maintain purity in the rest of the program.

The concept of dereferencing doesn’t exist in the same way as in languages with explicit pointers. Instead, we use readIORef to get the value from a reference and writeIORef to modify it.

Haskell’s type system and functional nature make it less common and often unnecessary to use mutable references, as immutable data structures and pure functions are preferred for most tasks.