Skip to main content

Algebraic Data Types via Class Decorators

Project description

Overview

This package provides a class decorator for defining Algebraic Data Types (ADTs) as known from Haskell (data), OCaml (type), and Rust (enum).

Features

  • Simplicity. This package exports only a single definition: the adt class decorator:

    from adt import adt
    
  • Concision. Constructors are specified via class annotations, allowing for syntax comparable to Rust's enums:

    @adt
    class Event:
        MouseClick: [int, int]
        KeyPress:   {'key': str, 'modifiers': list[str]}
    
  • Pattern Matching via match is fully supported (Python >= 3.10):

    event = Event.KeyPress(key='a', modifiers=['shift'])
    match event:
        case Event.MouseClick(x, y):    print(f"Clicked at ({x}, {y}).")
        case Event.KeyPress(key, mods): print(f"Pressed key {key}.")
    
  • Named and unnamed constructor fields are supported:

    • Constructors with named fields, like KeyPress, are specified as a dict[str, type];
    • Constructors with unnamed fields, like MouseClick, are specified as a list[type];
    • Constructors with a single unnamed field can also be specified as a type;
    • Constructors with no fields are specified as the empty list.
  • Getters, Setters, and Instance-Checking methods are derived as an alternative to pattern matching, e.g.

    if event.is_mouse_click():
        print(f"Clicked at ({event._1}, {event._2}).")
    elif event.is_key_press():
        print(f"Pressed key {event.key}.")
    
  • Constructors are customizable dataclasses. The dataclass decorator derives many useful method implementations, e.g. structural equality and string-conversion.

    Additonal keyword arguments to adt are forwarded as keyword arguments to the dataclass annotations of all constructors:

    @adt(frozen=True)  # <-- Use @dataclass(frozen=True) for all constructors.
    class Event:
        MouseClick: [int, int]
        KeyPress:   {'key': str, 'modifiers': list[str]}
    
    event = Event.MouseClick(5, 10)
    event._0 = 42 # Error! Constructor dataclass is frozen. 
    
  • Constructors inherit from the decorated type. Making the constructors inherit from the decorated class, allows to define methods with pattern matching directly in the decorated class and call them on objects of the constructor classes:

    @adt
    class Event:
        MouseClick: [int, int]
        KeyPress:   {'key': str, 'modifiers': list[str]}
        
        def print(self):
            match self:
                case Event.MouseClick(x, y):    print(f"Clicked at ({event._1}, {event._2}).")
                case Event.KeyPress(key, mods): print(f"Pressed key {event.key}.")
    
    Event.MouseClick(5, 10).print()
    
  • Constructors can be exported into the global namespace.

    @adt(export=True)  # <-- Makes `Event.` prefixes optional for constructors.
    class Event:
        MouseClick: [int, int]
        KeyPress:   {'key': str, 'modifiers': list[str]}
        
        def print(self):
            match self:
                case MouseClick(x, y):    ... # <-- As promised: no `Event.MouseClick`!
                case KeyPress(key, mods): ... # <-- As promised: no `Event.KeyPress`!
    
  • Reflection. The decorated class has a static field constructors: dict[str, type] which maps the constructor names to their classes, e.g.

    key_event = Event.constructors['KeyPress'](key='a', modifiers=['shift'])
    

Translation

The code generated in the above example by the adt decorator for the Event ADT behaves equivalent to the following code, with the exception that the constructor classes are constructed anonymously, so the global namespace is not even temporarily polluted unless @adt(export=True) is used.

from dataclasses import dataclass

class Event:
    def __init__(self, *args, **kwargs):
        raise TypeError(
            "Tried to construct an ADT instead of one of it's constructors.")

    def is_mouse_click(self) -> bool:
        return isinstance(self, Event.MouseClick)

    def is_key_press(self) -> bool:
        return isinstance(self, Event.KeyPress)

@dataclass
class MouseClick(Event):
  _1: int
  _2: int

@dataclass
class KeyPress(Event):
  key: str
  modifiers: list[str]

Event.MouseClick = MouseClick
Event.KeyPress   = KeyPress
if not export:
    del MouseClick
    del KeyPress

Event.constructors = {
    'MouseClick': Event.MouseClick,
    'KeyPress': Event.KeyPress,
}

Related packages

The following compares this package to packages which aim to provide similar functionality:

  • algebraic-data-types also describes ADTs via class decorators and annotations, but does not support pattern matching via match, as it is aimed at older python versions. Also the package does not support named constructor parameters.

  • algebraic-data-type and UxADT does not support a concise definition via decorators and does not support pattern matching via match.

  • choicetypes implements similar functionality, but instead of having subclasses for the constructors, the __init__-method of the main ADT-Class takes a named argument for each constructor variant, which is more verbose, error-prone and does not have a straightforward way to support named constructor arguments.

  • match-variant supports pattern matching via match and realizes ADTs by inheriting from a base class called Variant that seems to process the annotations. It does not seem to support named constructor parameters and methods that check if the ADT is a certain constructor.

  • py-foldadt comes without any documentation and has unclear functionality. It defines various algebraic structures like semirings with unclear connection to ADTs.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

adt-decorators-0.2.11.tar.gz (6.4 kB view details)

Uploaded Source

File details

Details for the file adt-decorators-0.2.11.tar.gz.

File metadata

  • Download URL: adt-decorators-0.2.11.tar.gz
  • Upload date:
  • Size: 6.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.11.4

File hashes

Hashes for adt-decorators-0.2.11.tar.gz
Algorithm Hash digest
SHA256 e15046a2b9668d463a61639ee00992ab9822ec28794a4e20bd3c660024eb137e
MD5 a40ad4c8bb51920349f598e7c9a61c37
BLAKE2b-256 f1a41faf25067b2f10f87988c8734b20dd9d1429c63b88edecfcf070b3d5fee5

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page