Python tagged unions based on dataclasses
I have developed a library to introduce tagged unions to python that uses dataclasses to define disjoint members of the tagged union (by defining them as Optional fields). With some additional validation I make sure that only one of the fields is not None. I find that it also fits well with the existing analysis tools and IDEs (e.g. PyCharm) and doesn’t require any additional work in order to be supported. I would like to see some criticism and whether that could potentially be a good candidate for python standard library in the future. -- Andrey Cizov
Sorry I forgot to add the URL: https://pypi.org/project/tagged-dataclasses/ On Fri, 22 May 2020 at 20:25, Andrey Cizov <acizov@gmail.com> wrote:
I have developed a library to introduce tagged unions to python that uses dataclasses to define disjoint members of the tagged union (by defining them as Optional fields). With some additional validation I make sure that only one of the fields is not None.
I find that it also fits well with the existing analysis tools and IDEs (e.g. PyCharm) and doesn’t require any additional work in order to be supported.
I would like to see some criticism and whether that could potentially be a good candidate for python standard library in the future.
-- Andrey Cizov
-- Andrey Cizov
Hello, On Fri, 22 May 2020 21:01:03 +0100 Andrey Cizov <acizov@gmail.com> wrote:
Sorry I forgot to add the URL: https://pypi.org/project/tagged-dataclasses/
How does this compare with many other implementations spread over the interwebs? As a quick comment, looks verbose comparing to ML ;-). For comparison, I found an algebraic2.py in ~/my-python-hacks/, no dataclasses or particular IDEs involved: -------------- from collections import namedtuple # Ideal syntax: # #Type = ( # Int(i: int) | # Str(s: str) | # Plus(l: Type, r: Type) #) def V(name, **args): return namedtuple(name, args.keys()) class Union: def __init__(self, *variants): self.variants = list(variants) def add(self, *variants): self.variants.extend(variants) def __instancecheck__(self, inst): return isinstance(inst, tuple(self.variants)) def match(val, typ): if isinstance(val, typ): # Have to return scalar instead of a tuple due to CPython # deficiency with := operator return val[0] -------------- Can be used as: -------------- UnaryExpr = Union( Int := V("Int", i=int), Str := V("Str", s=str), ) # Recursive variants should be added after initial definition UnaryExpr.add( Inc := V("Inc", e=UnaryExpr), Dec := V("Dec", e=UnaryExpr), ) def eval(var: UnaryExpr): if i := match(var, Int): return i elif s := match(var, Str): return s elif e := match(var, Inc): return eval(e) + 1 elif e := match(var, Dec): return eval(e) - 1 expr = Dec(Int(123)) print(isinstance(expr, UnaryExpr)) print(eval(expr)) -------------- It's sad "UnaryExpr" (instead of e.g. BinaryExpr) because of a known deficiency of CPython: --- $ python3.8 -c "( (a, b) := (1, 2) )" File "<string>", line 1 SyntaxError: cannot use assignment expressions with tuple --- Btw, does anybody know a Python implementation which has this bug fixed?
On Fri, 22 May 2020 at 20:25, Andrey Cizov <acizov@gmail.com> wrote:
I have developed a library to introduce tagged unions to python that uses dataclasses to define disjoint members of the tagged union (by defining them as Optional fields). With some additional validation I make sure that only one of the fields is not None.
I find that it also fits well with the existing analysis tools and IDEs (e.g. PyCharm) and doesn’t require any additional work in order to be supported.
I would like to see some criticism and whether that could potentially be a good candidate for python standard library in the future.
[] -- Best regards, Paul mailto:pmiscml@gmail.com
Most of the prior art to this tries to take too much from other languages (e.g. using match as you show here). For example they create their own DSL for matching that either introduces the magic function for matching, which doesn’t completely support Python classes (e.g what if I want my tagged union to also have methods?). I am not completely against the compactness of ML - however ML approaches usually imply either heavy customisation or support from the language itself. So I view my implementation as a pragmatic approach to implementing fully-featured tagged unions today: - support introspection in PyCharm - typecheckable with mypy - you can subclass from them - clear semantics It’s also super-lightweight! On Fri, 22 May 2020 at 23:03, Paul Sokolovsky <pmiscml@gmail.com> wrote:
Hello,
On Fri, 22 May 2020 21:01:03 +0100 Andrey Cizov <acizov@gmail.com> wrote:
Sorry I forgot to add the URL: https://pypi.org/project/tagged-dataclasses/
How does this compare with many other implementations spread over the interwebs?
As a quick comment, looks verbose comparing to ML ;-).
For comparison, I found an algebraic2.py in ~/my-python-hacks/, no dataclasses or particular IDEs involved:
-------------- from collections import namedtuple
# Ideal syntax: # #Type = ( # Int(i: int) | # Str(s: str) | # Plus(l: Type, r: Type) #)
def V(name, **args): return namedtuple(name, args.keys())
class Union:
def __init__(self, *variants): self.variants = list(variants)
def add(self, *variants): self.variants.extend(variants)
def __instancecheck__(self, inst): return isinstance(inst, tuple(self.variants))
def match(val, typ): if isinstance(val, typ): # Have to return scalar instead of a tuple due to CPython # deficiency with := operator return val[0] --------------
Can be used as:
-------------- UnaryExpr = Union( Int := V("Int", i=int), Str := V("Str", s=str), )
# Recursive variants should be added after initial definition UnaryExpr.add( Inc := V("Inc", e=UnaryExpr), Dec := V("Dec", e=UnaryExpr), )
def eval(var: UnaryExpr): if i := match(var, Int): return i elif s := match(var, Str): return s elif e := match(var, Inc): return eval(e) + 1 elif e := match(var, Dec): return eval(e) - 1
expr = Dec(Int(123)) print(isinstance(expr, UnaryExpr)) print(eval(expr)) --------------
It's sad "UnaryExpr" (instead of e.g. BinaryExpr) because of a known deficiency of CPython:
--- $ python3.8 -c "( (a, b) := (1, 2) )" File "<string>", line 1 SyntaxError: cannot use assignment expressions with tuple ---
Btw, does anybody know a Python implementation which has this bug fixed?
On Fri, 22 May 2020 at 20:25, Andrey Cizov <acizov@gmail.com> wrote:
I have developed a library to introduce tagged unions to python that uses dataclasses to define disjoint members of the tagged union (by defining them as Optional fields). With some additional validation I make sure that only one of the fields is not None.
I find that it also fits well with the existing analysis tools and IDEs (e.g. PyCharm) and doesn’t require any additional work in order to be supported.
I would like to see some criticism and whether that could potentially be a good candidate for python standard library in the future.
[]
-- Best regards, Paul mailto:pmiscml@gmail.com
-- Andrey Cizov
Hello, On Sat, 23 May 2020 21:52:47 +0900 "Stephen J. Turnbull" <turnbull.stephen.fw@u.tsukuba.ac.jp> wrote:
Paul Sokolovsky writes:
Andrey Cizov <acizov@gmail.com> wrote:
Sorry I forgot to add the URL: https://pypi.org/project/tagged-dataclasses/
As a quick comment, looks verbose comparing to ML ;-).
Do you mean ML, the language?
Yes, ML, the programming language: https://en.wikipedia.org/wiki/ML_(programming_language) , which is arguably the language which popularized (ahem, but in 40+ years, the effects are definitely visible) algebraic data types and pattern matching on them. -- Best regards, Paul mailto:pmiscml@gmail.com
participants (3)
-
Andrey Cizov
-
Paul Sokolovsky
-
Stephen J. Turnbull