Functional languages should be so much better at mutation than they are

Functional programming’s traditional emphasis on immutability is being re‑examined in light of performance and ergonomics questions around mutation. Commenters contrast Haskell’s purity and monadic effects with more mutation‑friendly designs in OCaml, F#, Rust, Koka, Roc and others, debating techniques like uniqueness types, linear types, STM, copy‑on‑write and compiler optimizations such as SSA and fusion. Many argue that mutation itself is not the problem—uncontrolled aliasing and “spooky action at a distance” are—so the real challenge is designing type systems and effect systems that allow local, efficient state changes without sacrificing reasoning or safety.

Mutability in ML-family and other FP languages

  • Several comments argue the article mischaracterizes OCaml/ML: mutation is well-supported and used when it’s the best tool, especially for arrays and unboxed floats.
  • Preference for immutable code is framed as trusting the compiler and GC to optimize, not fear of mutation.
  • F# is cited as a pragmatic example: mostly functional style, but mutability is common at boundaries and for performance.

Haskell, purity, and ergonomics

  • Multiple posters see the article as really about Haskell’s design: strict segregation of effects, monadic I/O, and laziness.
  • Critiques: poor performance on real hardware (space leaks, indirection), awkward everyday mutation patterns (State, ST, lenses), and steep learning curve for “big-tech” engineering.
  • Others defend Haskell’s purity and monadic approach as giving strong semantic guarantees; the real question is whether the cost is worth it.

Monads, ST/State, STM, and effect systems

  • Some say the article underestimates ST, State, and STM; these are widely used to implement mutable arrays, hash maps, and concurrent mutations in a controlled way.
  • Objections to ST focus on difficulty mixing with other effects; monad transformers help but have ordering and boilerplate issues.
  • Effect systems like Bluefin and algebraic effects are mentioned as ways to compose state, exceptions, streams, and I/O more flexibly.

Linear / uniqueness types and Rust-style ownership

  • Distinction is drawn between linear types (“must use exactly once”) and uniqueness types (“must be unaliased to mutate”).
  • Rust is presented as largely solving local mutability by requiring exclusive, non-aliased access for mutation; this prevents “spooky” shared updates.
  • Clean, Granule, Roc, Koka, and planned OCaml extensions explore uniqueness/linearity to enable safe in-place updates and reuse.

Copy-on-write, reference counting, and compiler optimizations

  • Copy-on-write with reference counting is common (Swift, Tcl, Roc, jq); it can fall into quadratic behavior when refactoring introduces extra aliases.
  • Some argue static analysis (SSA, uniqueness) can often avoid runtime reference counts and enable in-place mutation under the hood.
  • Koka’s FBIP optimization and “one-bit reference counts” for tracing GCs are discussed as hybrids.

Lists vs arrays and performance

  • Debate over whether idiomatic list-based OCaml is “as fast as” mutable arrays.
  • Microbenchmarks show hand-written dynamic arrays can outperform lists significantly; OCaml’s built-in Dynarray trades speed for stronger guarantees.
  • Tail-call optimization, “tail-mod-cons”, and GC behavior (minor heap optimized for immutability) complicate simple claims about arrays always winning.

Exceptions vs Result-style errors

  • Discussion contrasts exceptions with sum-type results (Result/Either).
  • Points raised: exceptions propagate silently and usually carry stack traces; Result forces explicit handling but often loses the creation-site trace.
  • In Haskell, runtime exceptions exist alongside Either; laziness makes exception semantics subtle and handling from pure code impossible.

Garbage collection vs reference counting

  • Strong back-and-forth: some claim tracing GC is “massively worse” than ref counting; others counter that modern tracing GCs usually beat RC on throughput.
  • Trade-offs are laid out: GC needs more memory but can make allocation/free cheap; RC imposes overhead on every pointer update and struggles with cycles.
  • Real-time and concurrent GCs, arena allocators, and fragmentation are cited as key design dimensions.

Broader views on FP and mutation

  • Several comments reframe FP as managing, not eliminating, mutation: aim for a functional core with imperative/mutable “shells.”
  • Others are skeptical of FP as a whole, arguing many of its benefits can be achieved with message-passing, encapsulation, and disciplined OO.
  • There is general agreement that mutation is necessary; the central question is how languages and type systems help constrain and reason about it.