{"id":17279551,"url":"https://github.com/jspahrsummers/adt","last_synced_at":"2025-04-06T07:12:17.743Z","repository":{"id":51054272,"uuid":"193975539","full_name":"jspahrsummers/adt","owner":"jspahrsummers","description":"Algebraic data types for Python (experimental, not actively maintained)","archived":false,"fork":false,"pushed_at":"2021-05-25T12:26:33.000Z","size":108,"stargazers_count":168,"open_issues_count":17,"forks_count":14,"subscribers_count":9,"default_branch":"master","last_synced_at":"2024-04-27T02:43:50.124Z","etag":null,"topics":["algebraic-data-types","python3","sum-types","tagged-unions"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jspahrsummers.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-06-26T20:48:24.000Z","updated_at":"2024-04-24T15:44:36.000Z","dependencies_parsed_at":"2022-08-24T18:50:50.411Z","dependency_job_id":null,"html_url":"https://github.com/jspahrsummers/adt","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jspahrsummers%2Fadt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jspahrsummers%2Fadt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jspahrsummers%2Fadt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jspahrsummers%2Fadt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jspahrsummers","download_url":"https://codeload.github.com/jspahrsummers/adt/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247445671,"owners_count":20939958,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["algebraic-data-types","python3","sum-types","tagged-unions"],"created_at":"2024-10-15T09:17:57.919Z","updated_at":"2025-04-06T07:12:17.723Z","avatar_url":"https://github.com/jspahrsummers.png","language":"Python","funding_links":[],"categories":["Awesome Functional Python"],"sub_categories":["Libraries"],"readme":"# adt [![CircleCI](https://circleci.com/gh/jspahrsummers/adt.svg?style=svg\u0026circle-token=2652421c13c636b5da0c992d77ec2fb0b128dd49)](https://circleci.com/gh/jspahrsummers/adt)\n\n`adt` is a library providing [algebraic data types](https://en.wikipedia.org/wiki/Algebraic_data_type) in Python, with a clean, intuitive syntax, and support for [`typing`](https://docs.python.org/3/library/typing.html) through a [mypy plugin](#mypy-plugin).\n\n_**NOTE:** This project is experimental, and not actively maintained by the author. Contributions and forking are more than welcome._\n\n**Table of contents:**\n\n1. [What are algebraic data types?](#what-are-algebraic-data-types)\n    1. [Pattern matching](#pattern-matching)\n    1. [Compared to Enums](#compared-to-enums)\n    1. [Compared to inheritance](#compared-to-inheritance)\n    1. [Examples in other programming languages](#examples-in-other-programming-languages)\n1. [Installation](#installation)\n    1. [mypy plugin](#mypy-plugin)\n1. [Defining an ADT](#defining-an-adt)\n    1. [Generated functionality](#generated-functionality)\n    1. [Custom methods](#custom-methods)\n\n# What are algebraic data types?\n\nAn [algebraic data type](https://en.wikipedia.org/wiki/Algebraic_data_type) (also known as an ADT) is a way to represent multiple variants of a single type, each of which can have some data associated with it. The idea is very similar to [tagged unions and sum types](https://en.wikipedia.org/wiki/Tagged_union), which in Python are represented as [Enums](#compared-to-enums).\n\nADTs are useful for a variety of data structures, including binary trees:\n\n```python\n@adt\nclass Tree:\n    EMPTY: Case\n    LEAF: Case[int]\n    NODE: Case[\"Tree\", \"Tree\"]\n```\n\nAbstract syntax trees (like you might implement as part of a parser, compiler, or interpreter):\n\n```python\n@adt\nclass Expression:\n    LITERAL: Case[float]\n    UNARY_MINUS: Case[\"Expression\"]\n    ADD: Case[\"Expression\", \"Expression\"]\n    MINUS: Case[\"Expression\", \"Expression\"]\n    MULTIPLY: Case[\"Expression\", \"Expression\"]\n    DIVIDE: Case[\"Expression\", \"Expression\"]\n```\n\nOr more generic versions of a variant type, like an `Either` type that represents a type A or a type B, but not both:\n\n```python\nL = TypeVar('L')\nR = TypeVar('R')\n\n@adt\nclass Either(Generic[L, R]):\n    LEFT: Case[L]\n    RIGHT: Case[R]\n```\n\n## Pattern matching\n\nNow, defining a type isn't that interesting by itself. A lot of the expressivity of ADTs arises when you [pattern match](https://en.wikipedia.org/wiki/Pattern_matching) over them (sometimes known as \"destructuring\").\n\nFor example, we could use the `Either` ADT from above to implement a sort of error handling:\n\n```python\n# Defined in some other module, perhaps\ndef some_operation() -\u003e Either[Exception, int]:\n    return Either.RIGHT(22)  # Example of building a constructor\n\n# Run some_operation, and handle the success or failure\ndefault_value = 5\nunpacked_result = some_operation().match(\n    # In this case, we're going to ignore any exception we receive\n    left=lambda ex: default_value,\n    right=lambda result: result)\n```\n\n_Aside: this is very similar to how error handling is implemented in languages like [Haskell](https://www.haskell.org/), because it avoids the unpredictable control flow of raising and catching exceptions, and ensures that callers need to make an explicit decision about what to do in an error case._\n\nOne can do the same thing with the `Expression` type above (just more cases to match):\n\n```python\ndef handle_expression(e: Expression):\n    return e.match(\n        literal=lambda n: ...,\n        unary_minus=lambda expr: ...,\n        add=lambda lhs, rhs: ...,\n        minus=lambda lhs, rhs: ...,\n        multiply=lambda lhs, rhs: ...,\n        divide=lambda lhs, rhs: ...)\n```\n\n## Compared to Enums\n\nADTs are somewhat similar to [`Enum`s](https://docs.python.org/3/library/enum.html) from the Python standard library (in fact, the uppercase naming convention is purposely similar).\n\nFor example, an `Enum` version of `Expression` might look like:\n\n```python\nfrom enum import Enum, auto\nclass EnumExpression(Enum):\n    LITERAL = auto()\n    UNARY_MINUS = auto()\n    ADD = auto()\n    MINUS = auto()\n    MULTIPLY = auto()\n    DIVIDE = auto()\n```\n\nHowever, this doesn't allow data to be associated with each of these enum values. A particular value of `Expression` will tell you about a _kind_ of expression that exists, but the operands to the expressions still have to be stored elsewhere.\n\nFrom this perspective, ADTs are like `Enum`s that can optionally have data associated with each case.\n\n## Compared to inheritance\n\nAlgebraic data types are a relatively recent introduction to object-oriented programming languages, for the simple reason that inheritance can replicate the same behavior.\n\nContinuing our examples with the `Expression` ADT, here's how one might represent it with inheritance in Python:\n\n```python\nfrom abc import ABC\nclass ABCExpression(ABC):\n    pass\n\nclass LiteralExpression(ABCExpression):\n    def __init__(self, value: float):\n        pass\n\nclass UnaryMinusExpression(ABCExpression):\n    def __init__(self, inner: ABCExpression):\n        pass\n\nclass AddExpression(ABCExpression):\n    def __init__(self, lhs: ABCExpression, rhs: ABCExpression):\n        pass\n\nclass MinusExpression(ABCExpression):\n    def __init__(self, lhs: ABCExpression, rhs: ABCExpression):\n        pass\n\nclass MultiplyExpression(ABCExpression):\n    def __init__(self, lhs: ABCExpression, rhs: ABCExpression):\n        pass\n\nclass DivideExpression(ABCExpression):\n    def __init__(self, lhs: ABCExpression, rhs: ABCExpression):\n        pass\n```\n\nThis is noticeably more verbose, and the code to consume these types gets much more complex as well:\n\n```python\ne: ABCExpression = UnaryMinusExpression(LiteralExpression(3))  # Example of creating an expression\n\nif isinstance(e, LiteralExpression):\n    result = ... # do something with e.value\nelif isinstance(e, UnaryMinusExpression):\n    result = ... # do something with e.inner\nelif isinstance(e, AddExpression):\n    result = ... # do something with e.lhs and e.rhs\nelif isinstance(e, MinusExpression):\n    result = ... # do something with e.lhs and e.rhs\nelif isinstance(e, MultiplyExpression):\n    result = ... # do something with e.lhs and e.rhs\nelif isinstance(e, DivideExpression):\n    result = ... # do something with e.lhs and e.rhs\nelse:\n    raise ValueError(f'Unexpected type of expression: {e}')\n```\n\nADTs offer a simple way to define a type which is _one of a set of possible cases_, and allowing data to be associated with each case and packed/unpacked along with it.\n\n## Examples in other programming languages\n\nAlgebraic data types are very common in functional programming languages, like [Haskell](https://www.haskell.org/) or [Scala](https://www.scala-lang.org/), but they're gaining increasing acceptance in \"mainstream\" programming languages as well.\n\nHere are a few examples.\n\n### [Rust](https://www.rust-lang.org/)\n\nRust `enum`s are actually full-fledged ADTs. Here's how an `Either` ADT could be defined:\n\n```rust\nenum Either\u003cL, R\u003e {\n    Left(L),\n    Right(R),\n}\n```\n\n### [Swift](https://developer.apple.com/swift/)\n\n[Swift enumerations](https://docs.swift.org/swift-book/LanguageGuide/Enumerations.html) are very similar to Rust's, and behave like algebraic data types through their support of \"associated values.\"\n\n```swift\nenum Either\u003cL, R\u003e {\n    case Left(L)\n    case Right(R)\n}\n```\n\n### [TypeScript](https://en.wikipedia.org/wiki/Microsoft_TypeScript)\n\n[TypeScript](https://en.wikipedia.org/wiki/Microsoft_TypeScript) offers ADTs through a language feature known as [\"discriminated unions\"](https://www.typescriptlang.org/docs/handbook/advanced-types.html#discriminated-unions).\n\nSee this example from their documentation:\n\n```typescript\ninterface Square {\n    kind: \"square\";\n    size: number;\n}\ninterface Rectangle {\n    kind: \"rectangle\";\n    width: number;\n    height: number;\n}\ninterface Circle {\n    kind: \"circle\";\n    radius: number;\n}\n\ntype Shape = Square | Rectangle | Circle;\n```\n\n# Installation\n\nTo add `adt` as a library in your Python project, simply run `pip` (or `pip3`, as it may be named on your system):\n\n```\npip install algebraic-data-types\n```\n\nThis will install [the latest version from PyPI](https://pypi.org/project/algebraic-data-types/).\n\n## mypy plugin\n\nThe library also comes with a plugin for [mypy](http://mypy-lang.org/) that enables typechecking of `@adt` definitions. **If you are already using mypy, the plugin is required to avoid nonsensical type errors.**\n\nTo enable the `adt` typechecking plugin, add the following to a `mypy.ini` file in your project's working directory:\n\n```\n[mypy]\nplugins = adt.mypy_plugin\n```\n\n# Defining an ADT\n\nTo begin defining your own data type, import the `@adt` decorator and `Case[…]` annotation:\n\n[//]: # (README_TEST:AT_TOP)\n```python\nfrom adt import adt, Case\n```\n\nThen, define a new Python class, upon which you apply the `@adt` decorator:\n\n```python\n@adt\nclass MyADT1:\n    pass\n```\n\nFor each case (variant) that your ADT will have, declare a field with the `Case` annotation. It's conventional to declare your fields with ALL_UPPERCASE names, but the only true restriction is that they _cannot_ be lowercase.\n\n```python\n@adt\nclass MyADT2:\n    FIRST_CASE: Case\n    SECOND_CASE: Case\n```\n\nIf you want to associate some data with a particular case, list the type of that data in brackets after `Case` (similar to the `Generic[…]` and `Tuple[…]` annotations from `typing`). For example, to add a case with an associated string:\n\n```python\n@adt\nclass MyADT3:\n    FIRST_CASE: Case\n    SECOND_CASE: Case\n    STRING_CASE: Case[str]\n```\n\nYou can build cases with arbitrarily many associated pieces of data, as long as all the types are listed:\n\n```python\n@adt\nclass MyADT4:\n    FIRST_CASE: Case\n    SECOND_CASE: Case\n    STRING_CASE: Case[str]\n    LOTS_OF_DATA_CASE: Case[int, str, str, Dict[int, int]]\n```\n\nADTs can also be recursive—i.e., an ADT can itself be stored alongside a specific case—though the class name has to be provided in double quotes (a restriction which also applies to `typing`).\n\nA typical example of a recursive ADT is a linked list. Here, the list is also made generic over a type `T`:\n\n```python\nT = TypeVar('T')\n\n@adt\nclass LinkedList(Generic[T]):\n    NIL: Case\n    CONS: Case[T, \"LinkedList[T]\"]\n```\n\nSee the library's [tests](tests/) for more examples of complete ADT definitions.\n\n## Generated functionality\n\nGiven an ADT defined as follows:\n\n```python\n@adt\nclass MyADT5:\n    EMPTY: Case\n    INTEGER: Case[int]\n    STRING_PAIR: Case[str, str]\n```\n\nThe `@adt` decorator will automatically generate accessor methods of the following form:\n\n```python\n    def empty(self) -\u003e None:\n        return None\n\n    def integer(self) -\u003e int:\n        ... # unpacks int value and returns it\n\n    def string_pair(self) -\u003e Tuple[str, str]:\n        ... # unpacks strings and returns them in a tuple\n```\n\nThese accessors can be used to obtain the data associated with the ADT case, but **accessors will throw an exception if the ADT was not constructed with the matching case**. This is a shorthand when you already know the case of an ADT object.\n\n`@adt` will also automatically generate a pattern-matching method, which can be used when you _don't_ know which case you have ahead of time:\n\n[//]: # (README_TEST:IGNORE)\n```python\n    Result = TypeVar('Result')\n    \n    def match(self,\n              empty: Callable[[], Result],\n              integer: Callable[[int], Result],\n              string_pair: Callable[[str, str], Result]) -\u003e Result:\n        if ... self was constructed as EMPTY ...:\n            return empty()\n        elif ... self was constructed as INTEGER ...:\n            return integer(self.integer())\n        elif ... self was constructed as STRING_PAIR ...:\n            return string_pair(*self.string_pair())\n        \n        # if pattern match is incomplete, an exception is raised\n```\n\nSee the library's [tests](tests/) for examples of using these generated methods.\n\n`@adt` will also generate `__repr__`, `__str__`, and `__eq__` methods (only if they are not [defined already](#custom-methods)), to make ADTs convenient to use by default.\n\n## Custom methods\n\nArbitrary methods can be defined on ADTs by simply including them in the class definition as normal.\n\nFor example, to build \"safe\" versions of the default accessors on `ExampleADT`, which return `None` instead of throwing an exception when the case is incorrect:\n\n```python\n@adt\nclass ExampleADT:\n    EMPTY: Case\n    INTEGER: Case[int]\n    STRING_PAIR: Case[str, str]\n\n    @property\n    def safe_integer(self) -\u003e Optional[int]:\n        return self.match(empty=lambda: None,\n                          integer=lambda n: n,\n                          string_pair=lambda _a, _b: None)\n\n    @property\n    def safe_string_pair(self) -\u003e Optional[Tuple[str, str]]:\n        return self.match(empty=lambda: None,\n                          integer=lambda n: None,\n                          string_pair=lambda a, b: (a, b))\n```\n\nHowever, additional fields _must not_ be added to the class, as the decorator will attempt to interpret them as ADT `Case`s (which will fail).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjspahrsummers%2Fadt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjspahrsummers%2Fadt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjspahrsummers%2Fadt/lists"}