Skip to content

Instantly share code, notes, and snippets.

@aarondewindt
Last active October 27, 2023 05:07
Show Gist options
  • Save aarondewindt/1cb0af45f05c0da03bfbec6f050f5b58 to your computer and use it in GitHub Desktop.
Save aarondewindt/1cb0af45f05c0da03bfbec6f050f5b58 to your computer and use it in GitHub Desktop.
Experimental Algebraic Data Types implementation in Python 3.10
from dataclasses import dataclass
class ADTMeta(type):
def __new__(mcs, name, bases, namespace: dict):
adtc_class = super().__new__(mcs, name, bases, namespace)
if "__is_adt_variant__" in namespace:
if namespace["__is_adt_variant__"]:
return adtc_class
for member_name, member in namespace.items():
if isinstance(member, type):
variant_class = dataclass(type(member_name, (adtc_class,), {
"__qualname__": f"{adtc_class.__qualname__}.{member_name}",
"__is_adt_variant__": True,
"__annotations__": member.__annotations__
}))
setattr(adtc_class, member_name, variant_class)
annotations = namespace.pop("__annotations__", {})
for variant_name, variant_annotation in annotations.items():
variant_class = type(variant_name, (adtc_class,), {
"__qualname__": f"{adtc_class.__qualname__}.{variant_name}",
"__is_adt_variant__": True
})
if (variant_annotation is Ellipsis) or (variant_annotation is None):
variant_class.__repr__ = lambda self: f"<{self.__class__.__qualname__}>"
else:
variant_class.__init__ = create_init(variant_class, variant_annotation)
variant_class.__repr__ = lambda self: f"<{self.__class__.__qualname__} {repr(self.value)}>"
variant_class.__match_args__ = ("value",)
setattr(adtc_class, variant_name, variant_class)
return adtc_class
def create_init(klass, annotation):
def __init__(self: klass, value: annotation) -> None:
self.value = value
return __init__
from adt import ADTMeta
class Foo(metaclass=ADTMeta):
a: ...
b: tuple[int, str, int]
c: str
class d:
da: str
db: int
dc: tuple[float, float]
assert issubclass(Foo.a, Foo)
assert issubclass(Foo.b, Foo)
assert issubclass(Foo.c, Foo)
assert issubclass(Foo.d, Foo)
value = Foo.a()
value = Foo.b((1, "Hello", 1234))
value = Foo.b((1, "World", 42))
# value = Foo.c("Bar")
# value = Foo.d("Baz", 69, (1.2, 3.5))
# value = Foo.d("positional", 69, (1.2, 3.5))
match value:
case Foo.a():
print("Matched Foo.a")
case Foo.b((val_int_1, val_str, 42)):
print(f"Matched Foo.b with {val_int_1} {val_str} and the answer of life, the universe and everything")
case Foo.b((val_int_1, val_str, val_int_2)):
print(f"Matched Foo.b with {val_int_1} {val_str} {val_int_2}")
case Foo.c(val_str):
print(f"Matched Foo.c with {val_str}")
case Foo.d("positional", val_int, (f1, f2)):
print(f"Matched d with positional pattern db={val_int} dc=({f1}, {f2})")
case Foo.d(da=val_str, db=val_int, dc=(f1, f2)):
print(f"Matched d with da={val_str} db={val_int} dc=({f1}, {f2})")
from adt import ADTMeta
# As simple Enum
class MouseButton(metaclass=ADTMeta):
Left: ...
Middle: ...
Right: ...
# As enum with parameters
class UserInput(metaclass=ADTMeta):
Nil: ...
KeyPress: str
KeyRelease: str
MouseClick: tuple[MouseButton, int, int]
def get_user_input() -> UserInput:
# You play around with this return value.
return UserInput.MouseClick((MouseButton.Middle, 42, 24))
match get_user_input():
case UserInput.Nil:
print("No input")
case UserInput.KeyPress(key):
print(f"Key {key} was pressed")
case UserInput.KeyRelease(key):
print(f"Key {key} was released")
case UserInput.MouseClick((MouseButton.Middle, x, y)):
print(f"The middle mouse button was clicked at ({x}, {y})")
case UserInput.MouseClick((button, x, y)):
print(f"Mouse clicked ({button}, {x}, {y})")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment