# A Haskeller's case for Zig

The author of [[pure-systems-blog]] has been writing Haskell for 10+ years and evaluates new languages on three axes:

1. How cleanly the language lets you express the program's domain (anything you write that isn't about the domain is *noise* — manual memory management is the canonical example).
2. What the language gives you for correct-by-construction programs and how programmable the type system is.
3. The [[mean-free-path-language]] — how many lines you can write before your understanding of the program diverges from what the program actually does.

Zig scores well on all three, and the post walks through why.

## The garbage-collection critique

GC was a defensible tradeoff in 1995 when CPU and memory speeds were comparable. Since then CPU has gotten roughly 10,000× faster while memory access has barely budged, but languages still assume a 1995 machine. The "forest of pointers into the heap" pattern that GC encourages caps performance.

There's also a cognitive cost the author flags: GC makes it too easy not to think about the machine. A generation of developers grew up never having to. The result is software that is bloated and slow on hardware that is effectively a supercomputer compared to what we had 30 years ago. (Compare [[peril-of-laziness-lost]] — Cantrill making the same charge from a different angle, that LLM coding removes the human-time constraint that used to force simplicity.)

The author cites Stephen Diehl's argument that nobody has the incentive to build a real new language: academics commit career suicide doing the engineering, industry can't fund decade-long projects, hobbyists don't have the time or money. So the field is stuck at a local maximum. Zig is interesting because Andrew Kelley's project "has the courage" to actually try.

## Comptime as type-system programming

The author wants Haskell-style type-system programming without paying for GC. Zig's [[comptime]] is the mechanism:

- **`newtype`** is just a singleton struct: `struct PlayerHealth { health: u32 }`.
- **Sum types** are tagged unions — there's even built-in optional syntax, and you can build your own `Maybe(T)` as a `comptime` function returning a `union(enum)` with `value: T` and `nothing` variants, with a `map` method that pattern-matches on the variant.
- **Typeclasses** are `comptime` functions that take a type, check it has the required methods (`if (!@hasDecl(T, "eql")) @compileError(...)`), and return a struct of wrapper functions. The catch is you have to pass the resulting struct around explicitly — `EqPoint.eql(a, b)` instead of `a == b`. That's the typeclass dictionary made manual. The clunkier version (you call `Eq(Point)` at the use site) can be cleaned up by having the type itself derive its own dictionary as a `pub const Eq = EqClass(@This())` field.

The dictionary-passing being explicit is *the point*, not a wart. Zig's stated philosophy is "no spooky action at a distance." Implicit typeclass dispatch is exactly the kind of action-at-a-distance the language refuses to do, and the author thinks that tradeoff is worth it.

## The accidental rediscovery of monads

Zig 0.16 reworked its IO subsystem to an interface design. The example in the release notes:

```zig
pub fn main(init: std.process.Init) !void {
    const gpa = init.gpa;
    const io = init.io;
    var http_client: std.http.Client = .{ .allocator = gpa, .io = io };
    ...
}
```

The author looks at this and sees a `Reader` monad carrying an allocator and an `IO` interface — the same shape Haskell's `IO` and `IO#` have. He frames monads not as "obscure math-y thing" but as the algebraic description of imperative programming as a computational context: you implement `MonadCont` to get imperative semantics, `LogicT` for nondeterminism with backtracking. The point is that not having a *built-in* notion of time gives the language more expressive ceiling and a higher optimization ceiling. Zig's team apparently arrived at the IO-passing pattern independently, which the author reads as confirmation that the pattern is universal.

(I don't fully buy the framing — passing an explicit `io` parameter is just dependency injection, and calling that "the IO monad" is a stretch unless you also have do-notation and a way to compose — but the underlying observation, that explicit effect-carrying is what both designs are doing, seems right.)

## Bottom line

The author's score:

- **Expressiveness**: comparable to Haskell, less noise than C++ or Rust for his use cases.
- **Type-system programming**: comptime is "at least in the ballpark of Haskell and easier to program at."
- **Mean-free path**: longer than Rust, much longer than C++. He hasn't been surprised by Zig semantics so far.

He's been a Haskell user for 10–13 years and is now redirecting energy to Zig.
