Haskell for Scala Developers: Part 5 — Optics and Deriving

This is the least philosophical piece you could write comparing Haskell and Scala, and the one you'll reach for most often. It's about the two things you actually do every Tuesday afternoon. Reach into a deeply nested immutable value and change one field three levels down. And get a type's boilerplate — JSON, equality, the lenses themselves — written for you instead of by hand.

Both languages solve both problems, and — this is the through-line — both solve them the same way underneath: by generating code from the shape of your types. Scala reaches for a lens library (Monocle) built on macros, and for derives clauses backed by Mirror and, when that runs out, inline/quotes. Haskell reaches for the optics ecosystem and for a deriving mechanism sitting on GHC.Generics, with Template Haskell as the full-power back door. Two languages, the same two destinations, arriving by different roads.

Two threads run through it: the record-update pain that makes optics worth having, and the deriving story, where Haskell's DerivingVia has no Scala equivalent at all.

Haskell for Scala Developers: Part 4 — Objects and Modules

Here's a thesis worth defending: most of Scala's object-oriented machinery is answers to questions Haskell never asks. Inheritance, trait linearization, the five different places a definition can live, private and its dozen qualifiers — a large fraction of it exists to manage complexity that a language built on data and functions simply doesn't generate.

That's the spicy half. Here's the fair half. Four of the things OOP was actually solving are real problems: polymorphic dispatch, code reuse, modularity, and controlled mutation with encapsulation. Every language needs answers to those. The argument of this post is not that Haskell ignores them — it's that Haskell answers each with something smaller and more honest than a class hierarchy, and that the smaller answer is usually the better one. Where Scala's version earns its keep, I'll say so; there are two or three places it genuinely does.

One running example the whole way down: a notification service. It's the canonical OOP teaching object — a polymorphic send, a hierarchy of channels, mixins that add retry and rate-limiting, and a private counter tracking deliveries. If you've sat through an "intro to OOP" course you've written this exact class. We're going to watch it dissolve into a handful of data types and functions, and I'll argue that what's left is what you actually wanted.

Haskell for Scala Developers: Part 3 — Effects and Concurrency

IO is a Monad in both Scala and Haskell, and do { line <- getLine; putStrLn line } is the same shape as for { line <- IO.readLine; _ <- IO.println(line) } yield (). That equivalence is where this post starts — with the part of the language that made half the Scala community pick up Cats Effect in the first place.

Here's the thesis, stated plainly: Cats Effect's IO is Haskell's IO, ported to the JVM. Not "inspired by." Ported. The referential transparency, the lazy description-of-a-computation, the run-it-at-the-edge model, bracket, Ref, race, fibers — all of it is the Haskell IO programming model rebuilt on a runtime that wasn't designed for it. Once you delete Future from your mental model, the distance between a Cats Effect program and the equivalent Haskell program is mostly tooling and syntax.

ZIO is the one place that story gets more interesting, and it's why I asked to cover both. ZIO's ZIO[R, E, A] is a genuinely different bet — it folds the environment and the error type into the effect type itself. Haskell makes that same bet too, but it spreads the pieces across different tools: ReaderT for the R, ExceptT or plain exceptions for the E, and increasingly an effect-handler library (effectful, polysemy, fused-effects) when you want the whole thing in one place. Same problem, different decomposition, real trade-offs on both sides.

The map: why Future is the thing to delete, what IO-as-a-value buys you, how errors become just another effect, resource safety, shared state, structured concurrency with a worked example, and finally ZIO against Haskell's MTL. The one neighbor I'm leaving for its own post is streaming — fs2 and ZIO Streams against conduit and streamly is a comparison that deserves the room.

Haskell for Scala Developers: Part 2 — Type Classes and Implicits

Functions, ADTs, options, newtypes — Scala does these well and Haskell does them slightly better, and the gap is small. Type classes are where it becomes wide.

Implicits in Scala have an origin story that I think gets understated. They are not "a way to thread context through your code" — that's how we use them, but it's not what they're for. Implicits were Scala's mechanism for doing the work of Haskell's type classes inside a language that doesn't have them. Wadler and Blott published the original Haskell type-class paper in 1989; Odersky designed Scala's implicit-parameter machinery explicitly so the same shape of code could be written on the JVM. The mechanism was reverse-engineered from the Haskell feature it was meant to imitate.

Scala 3's given / using is the cleanup. With the benefit of every Scala-incoherence horror story the community has collected, the language renamed the keywords, narrowed the resolution rules, and tried to hide the implicit-conversion traps that were eating juniors alive. It is much better than what it replaced. And it is still trying to be a thing that Haskell already is.

That's the post. Less ceremony, fewer traps, no implicit-conversion magic, instance coherence by default. We'll walk it from the simplest case — Eq, Show, Ord, same words and same job in both languages — through resolution and coherence, through the type-system feature both languages had to invent for this (HKTs), through the Functor/Applicative/Monad ladder on our own type, and ending with deriving. I'll assume you can read Cats fluently. I'm not going to re-teach the abstractions.

Haskell for Scala Developers: Part 1 — Functions and Data

There's a popular way to write this kind of post — a feature-by-feature comparison table, with checkmarks and a sentence per row. I'm not going to do that. What I want to do instead is take the parts of Scala that you already write idiomatically — case classes, sealed traits, Option, Either, the occasional extends AnyVal for a typed id — and show you what happens when the language stops apologizing for them.

That's the main thread here. Most of what made me love Scala is exactly what Haskell gives you, minus the JVM cost, minus the OOP escape hatches, and minus the long-running argument inside the Scala community about what good Scala even looks like. If you want the longer version of why I'm writing this at all, it's in The Rise and Fall of Scala.

This one is the easy one. Functions, data, pattern matching, options, newtypes — all the things you already type twenty times a week. The translation is mostly mechanical. The interesting part is the small set of frictions that disappear when the language stops compromising on them.