惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - Franky
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
美团技术团队
The Cloudflare Blog
量子位
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园_首页
F
Fortinet All Blogs
J
Java Code Geeks
人人都是产品经理
人人都是产品经理
N
Netflix TechBlog - Medium
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
爱范儿
爱范儿
Apple Machine Learning Research
Apple Machine Learning Research
B
Blog RSS Feed
博客园 - 聂微东
Hugging Face - Blog
Hugging Face - Blog
WordPress大学
WordPress大学
小众软件
小众软件
Y
Y Combinator Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Vercel News
Vercel News
S
SegmentFault 最新的问题
有赞技术团队
有赞技术团队

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
I Built Rust-Style ADTs in 30 Lines of Python (Pattern Ma...
Alexander Mi · 2026-05-12 · via DEV Community

Sum types — also called tagged unions or algebraic data types — are the feature I miss most when I switch from Rust or Haskell back to Python. The match statement landed in 3.10, but the standard library still does not give you a clean way to declare a closed set of variants where each variant carries its own fields.

Here is a 30-line metaclass that fixes that.

The result first

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")

Enter fullscreen mode Exit fullscreen mode

Three variants. Each variant is its own type. Pattern matching destructures fields by name. No Union, no isinstance chains, no boilerplate constructors.

The whole implementation

from dataclasses import make_dataclass, fields


class Case:
    def __init__(self, **attributes):
        self.dict = attributes


class EnumMeta(type):
    def __new__(cls, name, bases, clsdict):
        new_cls = super().__new__(cls, name, bases, clsdict)

        for field_name, value in clsdict.items():
            if not isinstance(value, Case):
                continue
            dc = make_dataclass(
                f"{name}.{field_name}",
                list(value.dict.items()),
                bases=(new_cls,),
            )
            dc.__match_args__ = tuple(f.name for f in fields(dc))
            setattr(new_cls, field_name, dc)

        return new_cls

Enter fullscreen mode Exit fullscreen mode

What is happening

Case is a placeholder. It records the fields a variant will carry and nothing else. Case(target=int) means this variant has one field named target typed as int.

EnumMeta walks the class body when the class is constructed. For every Case it finds, it does four things.

  1. Builds a dataclass for that variant with make_dataclass. The fields come straight from the Case kwargs.
  2. Inherits from the parent class. bases=(new_cls,) means Computation.To is a subclass of Computation. This is what makes match Computation.To(...) work as a class pattern.
  3. Sets __match_args__. This is the magic line. The match statement uses __match_args__ to know which positional fields to destructure. Dataclasses do not get this in the right shape by default for keyword-style patterns, so we set it explicitly from the field names.
  4. Replaces the Case placeholder on the class with the new dataclass.

After EnumMeta runs, Computation.List is no longer a Case — it is a real dataclass type. Calling Computation.List([1]) constructs an instance with targets=[1].

Why this beats the alternatives

Enum cannot carry per-variant fields. You would end up smuggling data through value tuples and losing type information.

Union[A, B, C] of dataclasses works for pattern matching, but you have to declare each variant as a separate top-level class and then wire them into a union by hand. The variants live everywhere; the union is a comment.

Libraries like returns or pyrsistent give you sum types but pull in a dependency and an opinionated style.

The metaclass approach keeps variants grouped under the parent type, so Computation is a closed namespace. You read the class definition and you see every possible value the type can take. That is the property that makes ADTs useful: exhaustiveness in one place.

Caveats

This is not exhaustive checking at the type level. mypy does not know Computation is closed, so a missing case in your match will not be flagged. If you want that, add a case _: assert_never(x) arm at the end.

make_dataclass does not accept forward references the way a typed dataclass body does. Stick to concrete types in Case(...) or pass strings and let dataclasses resolve them.

The variants are subclasses of the parent. That is load-bearing for match, but it also means isinstance(x, Computation) returns True for any variant, which you usually want.

When to reach for this

When you have a small, closed set of states that each carry different data. Parser results. State machine transitions. Validation outcomes. Anywhere you would write a chain of isinstance checks today.

For two states or a state without data, just use a dataclass with an Optional. For four or more variants with distinct payloads, the metaclass earns its keep.

Thirty lines. No dependencies. Real pattern matching.