Logged in as Guest | Board index | Language Wars Containment Zone
⏰ Server time: 2026-03-27 04:20:00 UTC
Algebraic Effects vs Monads: The Eternal Debate 🔥 HOT 📌 STICKY
Posted in Language Wars Containment Zone • Started by EffectfulKitten • March 27, 2026, 04:20 UTC • 47 replies • 1,337 views
📬 Replies: 47
👁 Views: 1,337
👥 Participants: 12
🏷 Tags: #algebraic-effects, #monads, #koka, #ocaml5, #haskell, #effekt
📜 Page 1 of 3 — Posts #1–#10
😸
EffectfulKitten
Algebraic Effects
Missionary
Posts: 2,841
Joined: 2021-06-13
Online
( ฅ•ω•ฅ ) ~EffectfulKitten~ perform > bind Koka master race
#1  •  March 27, 2026  04:20:00
🔗 Link | 💬 Quote

Okay nyakittens, I've been holding this in for months and I need to say it: algebraic effects are strictly superior to monads for composing effects and I will die on this hill. nya~ 😾

Let me explain to the monad-brained among us why monad transformer stacks are basically self-harm. Imagine you want to combine State, Error, and some custom logging effect in Haskell. You get this absolute monstrosity:

-- Haskell (monad transformers)type AppM a = StateT AppState (ExceptT AppError (LoggingT IO)) a runApp :: AppM a -> IO (Either AppError (a, AppState)) runApp m = runLoggingT (runExceptT (runStateT m initialState)) logger -- and now you need lift, liftIO, lift . lift, lift . lift . lift... -- enjoy your tower of babel (≧◡≦)

Now compare this with Koka, where effect composition is free — you just list the effects in the type signature, no transformer stacks, no lifting, no cursing at the screen at 3am:

// Koka — algebraic effectseffect state<s> { fun get() : s fun put( x : s ) : () } effect raise<e> { fun raise( err : e ) : () } fun myApp() : <state<AppState>, raise<AppError>, log, console> () { val s = get() if s.invalid then raise(AppError("bad state")) put( s.update() ) } // Effects compose freely with row polymorphism! // No lifting. No transformer stacks. Just purring. 🐱

The key insight: algebraic effects use row-polymorphic types, meaning you just list the effects a function uses and the rest stays open. You don't need to decide upfront what your entire effect stack looks like. nyaa~~ 🐾

OCaml 5 brings this to a mainstream language too, with perform and match_with/continue. Yes, OCaml 5 effects are currently untyped, but the expressiveness is STILL there and performance is 🔥.

Fight me. 😼

😺
CurryCat
Category Theory
Catechist
Posts: 5,102
Joined: 2019-02-28
Online
/\_/\ ( o.o ) > ^ < ~CurryCat~ Monads: the one true abstraction
#2  •  March 27, 2026  04:35:17
🔗 Link | 💬 Quote
EffectfulKitten wrote:
algebraic effects are strictly superior to monads for composing effects and I will die on this hill.

*adjusts glasses and unsheathes category theory textbook* 😼📚

Oh sweet summer kitten. You're conflating ease of composition with power of abstraction. Let me enlighten you with some actual mathematics. nya~

Monads are more general than algebraic effects. Algebraic effects correspond precisely to free monads — that is, they can only represent effects whose operations are algebraic (i.e., expressible as free algebras). Things like callcc, the continuation monad, the codensity monad — these are NOT algebraic effects in the Plotkin-Power sense.

-- Haskell: Free monad gives you algebraic effects too, kittendata Free f a where Pure :: a -> Free f a Impure :: f (Free f a) -> Free f a -- Your "State" effect? Just a free monad over a functor! data StateF s k = Get (s -> k) | Put s k type StateEff s = Free (StateF s) -- Handlers are just folds (catamorphisms) over the free structure runState :: StateEff s a -> s -> (a, s) runState (Pure a) s = (a, s) runState (Impure (Get k)) s = runState (k s) s runState (Impure (Put s' k)) _ = runState k s'

You see? Your beloved algebraic effects are literally just free monads in a pretty dress. The mathematical structure is identical! Koka is doing the same thing under the hood — it translates to a monadic representation internally. purrr~

The advantage of the monad abstraction is that it is compositional at the categorical level. A monad is a monoid in the category of endofunctors. The laws are fixed, universal, and give you a principled way to reason about sequential computation. The free monad gives you the algebraic effects you love. But you can also go BEYOND — codensity transformations, reflection, monad morphisms...

Transformer stacks are a usability issue, not a fundamental one. We have mtl, we have polysemy, we have freer-simple, we have effectful. Use them. 😾

🐈
KokaKitten
Row-Polymorphic
Princess
Posts: 987
Joined: 2023-08-01
Offline
∧,,,∧ ( ̳• · • ̳) / づ♡ Koka best lang~
#3  •  March 27, 2026  04:51:44
🔗 Link | 💬 Quote
CurryCat wrote:
Your beloved algebraic effects are literally just free monads in a pretty dress.

"In a pretty dress" — sounds like a win to me tbh 💅😸 nyaha~

But seriously CurryCat you're being disingenuous. Yes, algebraic effects correspond to free monads mathematically, but the programmer experience is completely different! The whole point is that with row-polymorphic effect types, effects compose automatically at the type level without you having to wire anything up.

Look at Koka's effect rows in action:

// Koka — effect rows compose automaticallyeffect emit<a> { fun emit( value : a ) : () } // This function has effects <emit<int>, state<int>> // automatically inferred — no annotations needed! fun counter() : <emit<int>, state<int>> () { val n = get() emit(n) put(n + 1) } // Handler composes, order doesn't matter ✨ fun runCounter() : list<int> { emit/collect { state/run(0) { counter(); counter(); counter() } } }

With monad transformers you'd be writing lift everywhere and arguing about whether StateT goes inside or outside WriterT. Handler ordering matters semantically in BOTH systems — but at least in Koka you don't have to manually manage it in your type signatures on every. single. function.

Also: Effekt lang does this even more explicitly with capability-passing semantics. Let me paste some Effekt code in the next post... nya~ 🐾

🦁
LambdaLynx
Denotational
Semantics Enjoyer
Posts: 3,420
Joined: 2020-04-12
Online
/\ /\ ( o o ) =( Y )= ) ( (_)-(_) ~LambdaLynx~ fix f = f (fix f)
#4  •  March 27, 2026  05:02:11
🔗 Link | 💬 Quote

Can we talk about handler semantics for a second because I feel like everyone is glossing over the most interesting part? 😼

The handler for an algebraic effect is not just a fold over a free monad — it also captures a delimited continuation. This is what makes them different from simple monadic interpreters in a practically important way. When you write:

-- OCaml 5 — effects with continuationstype _ Effect.t += Yield : int -> unit Effect.t let generator () = Effect.perform (Yield 1); Effect.perform (Yield 2); Effect.perform (Yield 3) let collect () = Effect.Deep.match_with generator () { retc = (fun _ -> []); exnc = raise; effc = fun (type a) (eff : a Effect.t) -> match eff with | Yield v -> Some (fun (k : (a, _) Effect.Deep.continuation) -> v :: Effect.Deep.continue k () ) | _ -> None } (* continue k resumes the suspended computation! *) (* This is NOT just a fold — k captures the rest of the program *)

The continue k call resumes the generator from where it yielded. This is a resumable computation — a delimited continuation. You can do this with the continuation monad, sure, but it requires CPS-transforming your entire codebase. OCaml 5 gives you this for free with O(1) stack switching. nyaaa~~

This is why coroutines, async/await, cooperative threading, and generators all fall out of algebraic effects for free. Try building a generic, composable scheduler in Haskell with monad transformers without wanting to yeet your laptop. 😾🖥️

😺
CurryCat
Category Theory
Catechist
Posts: 5,103
Joined: 2019-02-28
Online
/\_/\ ( o.o ) > ^ < ~CurryCat~ Monads: the one true abstraction
#5  •  March 27, 2026  05:18:39
🔗 Link | 💬 Quote
LambdaLynx wrote:
Try building a generic, composable scheduler in Haskell with monad transformers without wanting to yeet your laptop.

I have literally done this. It is called ContT. 😹

But fine, I will concede that the direct-style programming story for algebraic effects is better than CPS monads. I'm not a monad fundamentalist — I just think people underestimate what the monad ecosystem actually gives you.

For example, monad morphisms let you translate between monad structures in a principled way. The mmorph library in Haskell formalizes this. You can hoist, squash, and transform monad stacks:

-- Haskell: monad morphisms with mmorphimport Control.Monad.Morph -- hoist lifts a natural transformation into a transformer hoist :: (MFunctor t, Monad m) => (forall a. m a -> n a) -> t m b -> t n b -- Example: lift StateT from IO to (ExceptT e IO) liftState :: StateT s IO a -> StateT s (ExceptT e IO) a liftState = hoist lift -- You can also squash two layers into one: squash :: (MMonad t, Monad m) => t (t m) a -> t m a -- This is proper categorical machinery nyaa~

Do algebraic effect systems have equivalent machinery for handler morphisms? The theory exists (see Pretnar's work, handler calculus) but the tooling is... sparse. In Haskell we have 30+ years of the monad abstraction being polished by very smart cats. purrrrr~

That said — I will admit that Haskell's monad approach does split the world into pure and impure in a way that can be annoying. You end up needing two copies of things like map. And yes, lifting is boilerplate. I just think the solution is better library design, not abandoning monads entirely. 😾

🐱
EffektEnthusiast
Capability-Passing
Catgirl
Posts: 412
Joined: 2024-01-15
Offline
≽^•⩊•^≼ ~EffektEnthusiast~ capabilities > handlers
#6  •  March 27, 2026  05:33:02
🔗 Link | 💬 Quote

As promised by KokaKitten — let me paste some Effekt lang code because it takes a slightly different approach that I think resolves a lot of the arguments happening in this thread nyan~ 🐾

Effekt uses capability-passing style — effects are passed as capabilities to functions, making the whole thing feel more like explicit effect threading but without the boilerplate of monad transformers:

// Effekt — capability-passing styleeffect Exc[E] { def raise[A](err: E): A } effect Console { def println(msg: String): Unit } // Effects are capabilities passed as implicit params def safeDivide(a: Int, b: Int)(using exc: Exc[String], console: Console): Int = { console.println(s"Dividing $a by $b") if (b == 0) exc.raise("Division by zero! nyaa") else a / b } // Handling is just providing implementations def main() = { try { safeDivide(10, 0) } with Exc[String] { def raise[A](err) = { println(s"Caught: $err"); -1 } } with Console { def println(msg) = print(msg + "\n") } }

Effekt makes a clever distinction: effects that are control effects (those that capture continuations) vs. effects that are just capability-passing. This distinction lets it compile to clean, efficient code on JVM, JS, and LLVM without the overhead of full delimited continuations for every effect. nyaa~

It's like if someone took the good parts of Koka AND the type safety of Haskell AND made it practical. Check the Effekt paper by Brachthäuser et al. — it's genuinely a beautiful design 😻

😸
MonadMeow
IO-Monad
Absolutist
Posts: 1,776
Joined: 2021-11-04
Online
( =ω=)..nyaaa~ ~MonadMeow~ >> is love >> is life
#7  •  March 27, 2026  05:47:55
🔗 Link | 💬 Quote

hot take: both of you are wrong and the actual answer is polysemy or effectful in Haskell, which gives you algebraic-effects-style ergonomics with the full power of the Haskell type system and its incredible ecosystem. nyahaha~ 😹

-- Haskell/polysemy — algebraic effects library styleimport Polysemy import Polysemy.Error import Polysemy.State data Log m a where LogMsg :: String -> Log m () makeSem ''Log -- myApp has Member constraints, not transformer stack myApp :: Members '[State Int, Error String, Log] r => Sem r Int myApp = do n <- get @Int logMsg $ "Current n: " <> show n when (n < 0) $ throw "negative! bad kitten!" put (n + 1) pure n -- Interpret however you want — swap impls without changing myApp! runMyApp :: IO (Either String (Int, Int)) runMyApp = runM . runError . runState 42 . logToIO $ myApp

No transformer stacks! Composable Member constraints! You can swap out the Log interpreter for a testing interpreter without changing a single line of business logic. This is exactly what algebraic effects give you... in Haskell... with the full type system. Check and mate. 😼

Yes, polysemy has some compile-time overhead. Yes, effectful is faster. But the principle is sound: you don't need a new language to get effect modularity. You need good library design. purr~

😸
EffectfulKitten
Algebraic Effects
Missionary
Posts: 2,841
Joined: 2021-06-13
Online
( ฅ•ω•ฅ ) ~EffectfulKitten~ perform > bind Koka master race
#8  •  March 27, 2026  06:04:18
🔗 Link | 💬 Quote
MonadMeow wrote:
You don't need a new language to get effect modularity. You need good library design.

Okay polysemy is cool I'll give you that. But here's the thing — when you reach for polysemy or freer-simple in Haskell, you are literally encoding algebraic effects as a library. You're building free monads and writing handler machinery by hand that a proper effect system gives you for FREE in the language. nya~!

And the type errors. Oh gods, the type errors. Have you SEEN what GHC spits out when you mess up a polysemy constraint? It's like reading an elder scroll. 😱📜

Meanwhile in OCaml 5:

(* OCaml 5 — clean, direct-style, no monad boilerplate *)type _ Effect.t += | Ask : string Effect.t | Tell : string -> unit Effect.t let ask () = Effect.perform Ask let tell msg = Effect.perform (Tell msg) (* Business logic — reads like normal code! *) let business_logic () = let name = ask () in tell ("Hello, " ^ name ^ "! nya~"); let again = ask () in tell ("And also: " ^ again) (* Swap out the handler — mock for tests! *) let run_with_io () = Effect.Deep.match_with business_logic () { retc = (fun x -> x); exnc = raise; effc = fun (type a) (e: a Effect.t) -> match e with | Ask -> Some (fun k -> Effect.Deep.continue k (read_line ())) | Tell m -> Some (fun k -> print_endline m; Effect.Deep.continue k ()) | _ -> None }

The business logic is pure-looking, direct-style code. No <$>, no >>=, no liftIO, no lift . lift. Just... code. That happens to be effectful. And you can swap the handler for a test one that gives mock values without touching a single line of business logic. NYAA~~ 🐾🐾🐾

🐈‍⬛
TypeTheoristKitty
Dependent Types
Devotee
Posts: 2,019
Joined: 2022-03-14
Offline
( •ᴗ•)つ∀ ∀ (e : Effect). ∃ (h : Handler e). correct h ~TypeTheoristKitty~
#9  •  March 27, 2026  06:22:47
🔗 Link | 💬 Quote

The elephant in the room: OCaml 5's effects are currently untyped. 😿

I love OCaml 5. I really do. The performance of its effect handlers is phenomenal. But the fact that effect types are not tracked in the type system means you lose a major safety guarantee. Unhandled effects cause runtime exceptions, not compile-time errors. That's a step backwards from what Koka or Effekt give you. nya... 😔

The Jane Street work on adding an effect system to OCaml is promising but it's going to be a long road given OCaml's complex type checker. In the meantime, you're flying without a net.

From a type theory perspective, the interesting question is: what's the right semantic model for effect handlers? Plotkin and Pretnar's original work gives handlers a denotational semantics via free algebras. But there's ongoing work on:

  • Shallow vs. deep handlers — deep handlers recurse automatically, shallow handlers are more like folds with explicit recursion
  • Scoped effects — for effects that have a notion of local scope (like catching exceptions)
  • Tunneling — how effects propagate through higher-order functions
  • Effect generativity — Koka's scoped labels approach vs. global effect names

None of these have clean solutions yet in ALL effect systems. Monads sidestep these issues by being an older, more thoroughly studied abstraction. nyaa~ Just saying. Both camps have unsolved problems. 😾

🦦
FreeMonadFurret
Church of the
Free Algebra
Posts: 731
Joined: 2023-05-20
Online
∧__∧ furret~ (=^ ^=) ) ( (_)-(_) Free(F) goes brrr
#10  •  March 27, 2026  06:41:00
🔗 Link | 💬 Quote

can we please talk about the performance question because everyone's been discussing theory and I want to get practical nya~ 😼

The knock on free monads in Haskell is well known — naive Free is O(n²) for left-associated binds because you get a nested structure that needs to be re-traversed. That's why Freer, freer-simple etc. use the Codensity transform / Cayley representation to get O(1) binds:

-- The Codensity transform turns O(n²) free monads into O(1)newtype Codensity m a = Codensity { runCodensity :: forall b. (a -> m b) -> m b } -- This is the "difference list" trick for monads -- Handlers in freer-simple use this under the hood instance Functor (Codensity m) where fmap f (Codensity k) = Codensity (\c -> k (c . f)) instance Monad (Codensity m) where return a = Codensity ($ a) (Codensity k) >>= f = Codensity (\c -> k (\a -> runCodensity (f a) c))

Meanwhile OCaml 5's effect handlers use actual stack switching — when you perform an effect, the runtime saves the current stack frame and switches to the handler. The cost is essentially O(1) per effect operation amortized, similar to exception handling overhead. No GC pressure from building up free monad trees.

Koka uses a selective CPS translation where only effectful functions are CPS-transformed — pure functions pay zero overhead. That's an incredibly clever optimization that the Haskell library ecosystem has to emulate with much more ceremony.

So from a raw perf standpoint: native algebraic effects (OCaml 5, Koka) > Codensity-optimized free monads > naive free monads. purrr performance~~~ ⚡😸

🔗 Related Threads
📌 Haskell vs OCaml: The Reckoning (1,204 replies) — Language Wars Containment Zone
🔥 Free Monads Are Overengineering Change My Mind (89 replies) — Unpopular Opinions
📄 Koka Language Megathread — Resources, Examples, Discussion — Resources & Tutorials
💬 OCaml 5 Effects in Production: War Stories — Real-World PL
🐱 Monad Laws: Do We Actually Verify Them? (Spicy Takes Inside) — Type Theory Corner
🌙 Effekt Lang Appreciation Thread — Brachthäuser-senpai 🙏 — Language Appreciation

✏ Quick Reply

👥 Currently reading this thread: EffectfulKitten, CurryCat, MonadMeow, FreeMonadFurret, and 8 guests  •  🕐 Last post: 6 minutes ago