Programming Language Theory
IO Monads and Purity
How do you write a useful program without side effects? No print, no files, no network. Haskell's answer: you can't. But you can make side effects explicit in types so the compiler controls what happens where.
- **Facebook Sigma**: the anti-abuse system is written in Haskell. The IO monad cleanly separates pure detection logic (100% testable without mocks) from IO effects (DB, logs)
- **Standard Chartered Bank**: Haskell for financial computation. Purity guarantees that a derivative pricing function never accidentally logs confidential data
- **Purescript/Elm**: compile to JS with an IO monad. Elm rules out runtime exceptions entirely. You cannot accidentally perform a side effect outside the Effects system
The IO Monad
The IO monad in Haskell is the mechanism for safely embedding side effects in a pure language. IO a is a description of an action that, when executed, produces a value of type a. Functions returning IO never perform the effect themselves; they only describe it.
IO a is not an ordinary state-passing monad. It is a token that the Haskell runtime executes when main starts. The entire Haskell program is main :: IO (), a description of a sequence of IO actions.
What happens when you call putStrLn in Haskell?
Purity and Referential Transparency
A pure function: the same arguments always yield the same result, with no observable side effects. Referential transparency: a function call can be replaced by its result without changing the program. This is the basis of equational reasoning, reasoning about code by substitution.
What does 'referential transparency' mean?
Side Effects and Their Management
A side effect is any observable interaction with the outside world beyond the return value: writing to a database, an HTTP request, mutating global state. The IO monad makes side effects explicit in types. No surprises.
How do explicit side effects in types help the developer?
Do-notation and Monadic Code
Do-notation is syntactic sugar for >>= (bind). It makes monadic code look imperative. But every line in a do block is a monadic bind, not an ordinary assignment.
The IO monad in Haskell is just an over-engineered way to do what Python does in one line
The IO monad gives a static guarantee: a function without IO in its type NEVER performs side effects. The compiler enforces it
In Python there is no way to tell a pure function from a function with side effects without reading the code. In Haskell it is guaranteed by the type. This is critical for optimization, testing, and reasoning about code
What is the difference between <- and let in do-notation?
Takeaways
- **IO a**: a description of an effectful action that produces an a. It does not execute on 'call', only via main
- **Purity**: same inputs, same output, no observable effects. Referential transparency = a call can be replaced by its result
- **Explicit effects in types**: a function without IO is guaranteed pure. The compiler enforces it. No accidental side effects
- **Do-notation** is syntactic sugar for >>=. <- extracts from the monad, let is a pure binding
Related Topics
The IO monad is a special case of monads and effects:
- Monads and FP — IO is one monad; IO :: * -> * is an applicative functor
- Algebraic effects — Algebraic effects are a more flexible alternative to monads for managing effects
Вопросы для размышления
- Haskell's unsafePerformIO runs an IO action inside a pure context. When is that justified, and why is it called unsafe?
- Rust has no GC, allows mutable state, and yet exposes effects through types (&mut, Result). How close does its type system come to the guarantees of the IO monad?
- If the IO monad offers such guarantees, why has it not become mainstream in industrial languages? What is the real cost?