Here's an implementation of sum types in relatively Pythonic way.
import attr
@attr.s(frozen=True)
class CombineMode(object):
kind = attr.ib(type=str)
params = attr.ib(factory=list)
def match(self, expected_kind, f):
if self.kind == expected_kind:
return f(*self.params)
else:
return None
@classmethod
def join(cls):
return cls("join")
@classmethod
def select(cls, column: str):
return cls("select", params=[column])
Crack open an interpreter and you'll see familiar behavior:
>>> CombineMode.join()
CombineMode(kind='join_by_entity', params=[])
>>> CombineMode.select('a') == CombineMode.select('b')
False
>>> CombineMode.select('a') == CombineMode.select('a')
True
>>> CombineMode.select('foo').match('select', print)
foo
Note: The @attr.s
decorator comes from the attrs library, it implements __init__
, __repr__
, and __eq__
, but it also freezes the object. I included it because it cuts down on the implementation size, but it's also widely available and quite stable.
Sum types are sometimes called tagged unions. Here I used the kind
member to implement the tag. Additional per-variant parameters are implemented via a list. In true Pythonic fashion, this is duck-typed on the input & output sides but not strictly enforced internally.
I also included a match
function that does basic pattern matching. Type safety is also implemented via duck typing, a TypeError
will be raised if the passed lambda's function signature doesn't align with the actual variant you're trying to match on.
These sum types can be combined with product types (list
or tuple
) and still retain a lot of the critical functionality required for algebraic data types.
Problems
This doesn't strictly constrain the set of variants.