Developer Tools

Python ADTs in 30 Lines: Pattern Matching Realized

Forget clunky Union types. This 30-line Python metaclass delivers Rust-like sum types with real pattern matching. It's a game-changer for managing states.

Diagram showing Python code with a metaclass creating variant dataclasses for pattern matching

Key Takeaways

  • A 30-line Python metaclass implements Rust-style algebraic data types (sum types) with pattern matching.
  • The solution uses `dataclasses.make_dataclass` and a custom metaclass to dynamically create variant types.
  • This approach offers a dependency-free, elegant way to manage complex states and avoid `isinstance` chains in Python.

Forget the endless isinstance chains and Union gymnastics. Python 3.10 finally got match, a decent stab at pattern matching. But the standard library? Still a desert when it comes to declaring closed sets of variants, each with its own data. A glaring omission. Until now.

Then, like a well-timed case statement, 30 lines of Python code appear. A metaclass, to be precise. It’s a neat trick, stuffing the power of algebraic data types – those glorious sum types you find in Rust or Haskell – into a ridiculously small Python package.

Here’s the core idea: a placeholder class (Case) that just notes down what fields a variant should have. Then, a metaclass (EnumMeta) that, during class creation, transforms these placeholders into actual dataclasses. These new dataclasses magically become subclasses of the main type. It sounds like a lot of smoke and mirrors, but the result is elegantly functional.

class Computation(metaclass=EnumMeta):
    Nothing = Case()
    To = Case(target=int)
    List = Case(targets=list[int])

follower = Computation.List([1])

match follower:
    case Computation.To(target=p):
        print(p)
    case Computation.List(targets=p):
        print(p)
    case Computation.Nothing:
        print("nothing")

Three variants. Each is its own distinct type. And pattern matching? It destructs fields by name. No more boilerplate constructors. No more Union horrors. Just clean, readable code.

The Magic Behind the Curtain

The original article breaks down the EnumMeta metaclass and its Case placeholder. It’s not just about creating classes; it’s about how these new classes inherit from their parent. This inheritance is the key. It’s what allows match Computation.To(...) to work as a class pattern. The __match_args__ is another vital piece. It tells the match statement how to unpack positional arguments, something dataclasses don’t offer out of the box for keyword-style matching. The metaclass plugs this gap, making destructuring smooth.

Think about the alternatives. Standard Python Enum? Useless for per-variant data. You’d end up with a value attribute holding a tuple, a type-safety nightmare. Union of dataclasses? Sure, it works for matching, but you have to declare each variant separately, wire them up manually. The variants become scattered orphans. Libraries like returns or pyrsistent offer sum types, but that means adding a dependency. This 30-line solution is dependency-free.

Why This Matters for Python Developers?

This metaclass offers a closed namespace. When you define Computation, you see all possible values that type can take. That’s the power of ADTs. Exhaustiveness, right there in the class definition. This isn’t full compile-time exhaustiveness checking, mind you. mypy won’t flag a missing case. You’ll need a case _: assert_never(x) guard for that. But for runtime clarity and developer sanity, it’s a massive leap.

Where do you use this? Anywhere you’ve got a small, closed set of states with distinct data payloads. Parser results. State machine transitions. Validation outcomes. Anywhere you’re currently wrestling with isinstance chains. For two states, maybe a simple dataclass with Optional fields will do. But for four or more distinct states, this metaclass shines.

The variants live everywhere; the union is a comment.

This quote nails the pain point that this solution addresses. ADTs, when implemented correctly, group related states and their data cohesively. This Python metaclass achieves that without the baggage of external libraries or the clumsiness of built-in Python constructs for this specific problem.

Is This the Future of Python State Management?

It’s a bit early to declare a revolution. But it’s certainly a powerful demonstration of what’s possible with Python’s metaprogramming. It’s a clever hack, sure, but it solves a real problem elegantly. For projects that need strong state management without pulling in heavy dependencies, this is a compelling option. It makes Python feel a little more like those languages with superior type systems, without sacrificing its own unique flavour.

Frequent Asked Questions

What are Algebraic Data Types (ADTs) in Python? ADTs, or sum types, allow you to define a type that can be one of several distinct variants, each potentially carrying its own data. This metaclass approach enables this in Python.

Can this metaclass help with type checking in Python? While this metaclass enhances runtime pattern matching and code clarity, it does not provide compile-time exhaustiveness checking like static type checkers (e.g., mypy) might offer in other languages. You still need to manually add case _: assert_never(x) for full runtime safety.

Do I need to install any extra libraries for this to work? No. The provided solution uses only built-in Python features, specifically metaclasses and dataclasses, making it a dependency-free implementation.


🧬 Related Insights

Sam O'Brien
Written by

Ecosystem and language reporter. Tracks package releases, runtime updates, and OSS maintainer news.

Worth sharing?

Get the best Open Source stories of the week in your inbox — no noise, no spam.

Originally reported by Dev.to

Stay in the loop

The week's most important stories from Open Source Beat, delivered once a week.