Package: adt-decorators
OverviewThis package provides a class decorator for definingAlgebraic Data Types (ADTs)as known fromHaskell(data),OCaml(type), andRust(enum).FeaturesSimplicity.This package exports only a single definition: theadtclass decorator:fromadtimportadtConcision.Constructors are specified via class annotations,
allowing for syntax comparable to Rust'senums:@adtclassEvent:MouseClick:[int,int]KeyPress:{'key':str,'modifiers':list[str]}Pattern Matchingviamatchis fully supported (Python >= 3.10):event=Event.KeyPress(key='a',modifiers=['shift'])matchevent:caseEvent.MouseClick(x,y):print(f"Clicked at ({x},{y}).")caseEvent.KeyPress(key,mods):print(f"Pressed key{key}.")Named and unnamed constructor fieldsare supported:Constructors with named fields, likeKeyPress, are specified as adict[str, type];Constructors with unnamed fields, likeMouseClick,  are specified as alist[type];Constructors with a single unnamed field can also be specified as atype;Constructors with no fields are specified as the empty list.Getters, Setters, and Instance-Checkingmethods are derived as an alternative to pattern matching, e.g.ifevent.is_mouse_click():print(f"Clicked at ({event._1},{event._2}).")elifevent.is_key_press():print(f"Pressed key{event.key}.")Constructors are customizable dataclasses.Thedataclassdecorator derives many useful method implementations,
e.g. structural equality and string-conversion.Additonal keyword arguments toadtare forwarded as keyword
arguments to thedataclassannotations of all constructors:@adt(frozen=True)# <-- Use @dataclass(frozen=True) for all constructors.classEvent: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:@adtclassEvent:MouseClick:[int,int]KeyPress:{'key':str,'modifiers':list[str]}defprint(self):matchself:caseEvent.MouseClick(x,y):print(f"Clicked at ({event._1},{event._2}).")caseEvent.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.classEvent:MouseClick:[int,int]KeyPress:{'key':str,'modifiers':list[str]}defprint(self):matchself:caseMouseClick(x,y):...# <-- As promised: no `Event.MouseClick`!caseKeyPress(key,mods):...# <-- As promised: no `Event.KeyPress`!Reflection.The decorated class has a static fieldconstructors: dict[str, type]which maps the constructor names to their classes, e.g.key_event=Event.constructors['KeyPress'](key='a',modifiers=['shift'])TranslationThe code generated in the above example by theadtdecorator for theEventADT 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.fromdataclassesimportdataclassclassEvent:def__init__(self,*args,**kwargs):raiseTypeError("Tried to construct an ADT instead of one of it's constructors.")defis_mouse_click(self)->bool:returnisinstance(self,Event.MouseClick)defis_key_press(self)->bool:returnisinstance(self,Event.KeyPress)@dataclassclassMouseClick(Event):_1:int_2:int@dataclassclassKeyPress(Event):key:strmodifiers:list[str]Event.MouseClick=MouseClickEvent.KeyPress=KeyPressifnotexport:delMouseClickdelKeyPressEvent.constructors={'MouseClick':Event.MouseClick,'KeyPress':Event.KeyPress,}Related packagesThe following compares this package to packages which aim to provide similar functionality:algebraic-data-typesalso describes ADTs via class decorators and
annotations, but doesnotsupport pattern matching viamatch, as it is aimed
at older python versions. Also the package does not support named
constructor parameters.algebraic-data-typeandUxADTdoes not support a concise definition via decorators and does not
support pattern matching viamatch.choicetypesimplements
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-variantsupports
pattern matching viamatchand realizes ADTs by inheriting from a
base class calledVariantthat 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-foldadtcomes without
any documentation and has unclear functionality. It defines various
algebraic structures like semirings with unclear connection to ADTs.