Algebraic data types for Python.
Write a better readme
To define a new type, create a subclass of adt.ADT
where the class body contains the constructors.
A constructor is a capitalized name which may be parameterized on some types. Type variables are denoted with _1
, _2
, _3
, and so on.
The order of the types when we instantiate ADTs is the order of the type variable's number. So _1
is bound to the first type, _2
to the second and so on.
The following example have only imported ADT
from the adt
module. The constructor names and type variable names are not defined before hand.
Define an Either
type.
class Either(ADT):
Left(_1)
Right(_2)
To create instances of this type, we would do something like:
>>> Either[int, float].Left(1) # box an int value
Either[int, float].Left(1)
>>> Either[int, float].Right(1.5) # box a float value
Either[int, float].Right(1.5)
We cannot create invalid instances, for example:
>>> Either[int, float].Right(1) # the ``Right`` constructor takes floats,
... # not ints
Traceback (most recent call last):
...
TypeError: expected type 'float' for argument at position 0, got 'int': 1
Define a type with named values.
class Struct(ADT):
A(a=_1, b=_1)
B(a=_2, b=_2)
Instances must be created with keyword arguments, for example:
>>> Struct[int, float].A(a=1, b=2)
Struct[int, float].A(b=2, a=1)
We can access the fields by name:
>>> s = Struct[int, float].A(a=1, b=2)
>>> s.a
1
>>> s.b
2
We can create recursive structures:
class List(ADT):
Nil()
Cons(_1, List[_1])
It is enforced that the list on the right side of a Cons
cell holds the same type as the left side.
We create these types so that we may use them in case
statements.
Case statements have the following syntax:
from adt import case, match
@match(scrutinee)
class value(case):
Constructor1(arg1, arg2, ... argn) >> expr1
Constructor2(arg1, arg2, ... argn) >> expr2
...
Constructorn(arg1, arg2, ... argn) >> exprn
This says that when the expression scrutinee
is an instance of Constructorn
, then value
will be the result of executing exprn
with arg1, arg2, ... argn
in scope.
For example:
>>> @print
... @match(Either[int, float].Left(1))
... class _(case):
... Right(_1) >> float('nan')
... Left(_1) >> _1 + 1
2
>>> @print
... @match(Struct[int, float].A(a=1, b=2))
... class _(case):
... A(a=_1, b=_2) >> _1 + _2
... B(a=_1, b=_2) >> _1 - _2
3
Case alternatives are executed lazily, this means that only the alternative expression entered will be evaluated:
>>> @match(List[int].Cons(1, List[int].Nil()))
... class _(case):
... Nil() >> print('empty') # note: not printed!
... Cons(_1, _2) >> print('not empty')
not empty
This is valid python syntax.