What is the difference between ST effect / Reader / Writer ?

Welcome @librarian!
I’ll even add one to your list: the State monad!
I think the short answer is they all do very similar things or potentially the same thing, but they just communicate intent slightly differently. Imagine how in C# you might have a class property with a get and set operation, or you might have a “getter” method and a “setter” method, which do the same thing, but communicate a little differently to the consumer about things like expected runtime and usage. Or in JavaScript/TypeScript how you can have a Map with string keys or an Object, which pretty much do the same thing, but communicate a little differently to the consumer about what kind of contents they might expect.

I think the different stateful monads can be summed up as follows

  1. State is your go-to for most stateful computations, unless you have a reason to prefer some other monad.
  2. Reader is for (generally) read-only states, like application configs, where you set a value at the beginning and then don’t mutate it further. There’s the local function like you mentioned, which kind of lets you write to it, but you can’t return a modified value to the caller of a Reader, so it still kind of preserves its read-only status. You could replace a Reader with State and it just wouldn’t be read-only anymore.
  3. Writer is for (generally) write-only states, like logs, where you consume a value only at the end but don’t inspect the value anywhere while running. You could replace a Writer with State and it just wouldn’t be write-only anymore.
  4. RWS is for situations where you have different things that are readable, or writable, or otherwise stateful all at the same time. So like if you have a config, a log, and some application state that changes as the program executes. It’s really just a helpful alias for having a Reader, a Writer, and a State all together as part of the same stack. You could actually replace RWS with three State monads, or with a single State of a record that looked like
    { config :: r, logs :: w, appState :: s }.
  5. ST is very similar to State, but with performance optimizations at the cost of added complexity (you have to understand and manage the “Region” of mutation). This is generally something you use as a performance optimization in some algorithm when mutating a value in a tight loop is significantly faster than “pure FP” approaches. You wouldn’t often use ST as just a general approach for managing some state, since it’s harder to use than just the State monad.

Hope that’s helpful! Please ask if there’s further questions or other ways to clear things up.

5 Likes