import typing from typing import * @typing.sealed class Maybe(Generic[T]): """An Option like it ought to be: an ADT, not like Python's Optional[T]. """ # Ideally this would be named Option/None/Some for consistency with Rust, # but it clashes with Python names, so this uses Haskell names instead. __cache = {} def __class_getitem__(cls, type_param): """Generates Just and Nothing variants for the non-generic class.""" if type_param in cls.__cache: return cls.__cache[type_param] new_cls = type(f"Maybe[{type_param}]", (cls,), {}) @dataclass class Just(new_cls): item: type_param @classmethod def from_ast(cls, ast): return Just(type_param.from_ast(ast)) @dataclass class Nothing(new_cls): pass new_cls.Just = Just new_cls.Nothing = Nothing cls.__cache[type_param] = new_cls return new_cls T = typing.TypeVar("T") U = typing.TypeVar("U") def map_maybe(m: Maybe[T], f: typing.Callable[[T], U]) -> Maybe[U]: M = m.__class__ match m: case M.Nothing(): return M.Nothing() case M.Just(item): return M.Just(f(item)) case _: print("oops", repr(m)) print(map_maybe(Maybe[int].Nothing(), lambda x: x*2)) # Maybe.__class_getitem__..Nothing() print(map_maybe(Maybe[int].Just(21), lambda x: x*2)) # Maybe.__class_getitem__..Just(item=42)