Haskell for Scala Developers: Part 3 — Effects and Concurrency

Part 2 ended on a single line: IO is a Monad in both languages, and do { line <- getLine; putStrLn line } is the same shape as for { line <- IO.readLine; _ <- IO.println(line) } yield (). That's where this post starts. We've spent two posts on the parts of the language where Scala and Haskell mostly agree. This is the post about the part 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.

Previous in series: Part 2 — Type Classes and Implicits.

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

Part 1 was the friendly part of the comparison. Functions, ADTs, options, newtypes — Scala does these well, Haskell does them slightly better, and the gap is small. This post is where the gap 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.

Previous in series: Part 1 — Functions and Data.

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 of the series. 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 first post 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.

Haskell Was Waiting for This

Every few months someone announces "the best language for AI." Usually it's Python, sometimes TypeScript, occasionally Rust when the mood is right. I want to make a less fashionable claim: if you care about shipping code that an LLM helped you write, and you want to be confident it actually works, Haskell is quietly the best fit in 2026.

Not the trendiest. Not the easiest to hire for. The best fit. Let me explain.

A Philosophy for Personal Media File Storage

If you've been taking photos and videos for more than a few years, you've probably accumulated a mess. Files from phones named IMG_20230415_143022.jpg, Pixel cameras producing PXL_20230415_143022.mp4, screenshots labeled Screenshot_2023-04-15, and whatever your old point-and-shoot decided to call things. They're scattered across devices, cloud services, and backup drives — some with correct dates in their metadata, some without, some with dates that are just wrong.

At some point you try to organize them. You create folders: "Vacation 2023", "Family", "Work Events". It feels productive for a week. Then a photo belongs in two folders, or you can't remember whether that dinner was "Family" or "Friends", or you find a folder called "Misc" with 400 unsorted files from three years ago.

I went through this cycle a few times before stepping back and rethinking the problem.