adt icon indicating copy to clipboard operation
adt copied to clipboard

Algebraic data types for python

trafficstars

=== ADT

Algebraic data types for Python.

.. todo::

Write a better readme

Basic Usage

Defining Types

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.

Either[_1, _2]


Define an ``Either`` type.

.. code-block:: python

   class Either(ADT):
       Left(_1)
       Right(_2)


To create instances of this type, we would do something like:

.. code-block:: python

   >>> 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:

.. code-block:: python

   >>> 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


``Struct[_1, _2]``

Define a type with named values.

.. code-block:: python

class Struct(ADT): A(a=_1, b=_1) B(a=_2, b=_2)

Instances must be created with keyword arguments, for example:

.. code-block:: python

Struct[int, float].A(a=1, b=2) Struct[int, float].A(b=2, a=1)

We can access the fields by name:

.. code-block:: python

s = Struct[int, float].A(a=1, b=2) s.a 1 s.b 2

List[_1]


We can create recursive structures:

.. code-block::

   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.


Destructuring Types
-------------------

We create these types so that we may use them in ``case`` statements.

Case statements have the following syntax:

.. code-block:: python

   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:

.. code-block:: python

   >>> @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:

.. code-block:: python

   >>> @match(List[int].Cons(1, List[int].Nil()))
   ... class _(case):
   ...     Nil() >> print('empty')  # note: not printed!
   ...     Cons(_1, _2) >> print('not empty')
   not empty


Why?
====

This is valid python syntax.