Skip to content

Haskell Pure Func

DodaTech 3 min read

In this tutorial, you'll learn about Haskell Pure Func Quick Fix. We cover key concepts, practical examples, and best practices.

Haskell pure functions are the bedrock of referential transparency: calling a pure function with the same arguments always returns the same result. Breaking this contract with unsafePerformIO, global mutable state, or FFI calls causes unpredictable behavior and makes testing impossible.

The Wrong Way

-- Wrong: using unsafePerformIO makes this impure
import System.IO.Unsafe (unsafePerformIO)
import Data.IORef (newIORef, readIORef, modifyIORef')

globalCounter :: IORef Int
globalCounter = unsafePerformIO (newIORef 0)
{-# NOINLINE globalCounter #-}

impureAdd :: Int -> Int
impureAdd x = unsafePerformIO $ do
    modifyIORef' globalCounter (+1)
    c <- readIORef globalCounter
    return (x + c)

Output:

-- First call: impureAdd 5 returns 6
-- Second call: impureAdd 5 returns 7
-- NOT PURE! Same input, different output

The Right Way

-- Right: keep pure functions pure
pureAdd :: Int -> Int -> Int
pureAdd x y = x + y

-- Move state to explicit parameter
addWithCounter :: Int -> Int -> (Int, Int)
addWithCounter x count = (x + count + 1, count + 1)

Output:

pureAdd 5 0 → 5
pureAdd 5 0 → 5  -- Same input, same output every time

Step-by-Step Fix

1. Identify impurity sources

Look for unsafePerformIO, unsafeInterleaveIO, IORef, MVar, IOArray, FFI calls, System.Random outside IO, getCurrentTime, and readFile in pure contexts.

2. Isolate the impurity

Move impure operations to explicit IO contexts. Keep a thin impure layer at the edges (main, handlers) and pass data inward through pure functions.

3. Thread state explicitly

Instead of global mutable state, pass state as a parameter and return the updated state. Use State monad for implicit threading.

4. Use dependency injection

Pass configuration, random seeds, and other dependencies as explicit parameters. Use ReaderT for implicit dependency injection.

5. Test purity

A pure function is trivially testable: same input → same output. No mocking, no setup. If a test requires IO, the function is not pure.

Prevention Tips

  • Never use unsafePerformIO in production code meant to be pure
  • Keep IO at the application boundary (main, handlers, event loops)
  • Pass all dependencies as explicit parameters
  • Use State / ReaderT for managed state and configuration
  • Pure functions are easier to test, compose, and reason about

Common Mistakes with pure func

  1. Mixing let bindings with <- bindings in do notation, producing type errors
  2. Overlapping type class instances that cause GHC to reject the program with ambiguous dispatch errors
  3. Non-exhaustive pattern matches that compile with warnings then crash at runtime

These mistakes appear frequently in real-world HASKELL code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.

Practice Exercise

Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.

This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.

Real-World Use Case

Haskell is widely used in fintech, blockchain, and compiler development. Companies like Standard Chartered, IOHK, and Facebook use Haskell for production systems where correctness and maintainability are critical. This pattern appears in real-world Haskell codebases including those powering the DodaTech infrastructure stack. Understanding pure func correctly helps prevent bugs in production systems and makes your HASKELL code more maintainable.

FAQ

### What breaks referential transparency in Haskell?

unsafePerformIO, unsafeInterleaveIO, global IORefs/MVars, FFI calls to impure C functions, System.IO.Unsafe, reading system time, and random number generation outside IO.

How do I test pure functions?

Call the function with known inputs and assert on outputs using == or QuickCheck properties. Pure functions need no test fixtures, no mocking, and no IO setup.

Can I use random numbers in a pure function?

Not directly. Pass the random generator as a parameter and return the updated generator alongside the result, using State StdGen or split.

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro