Skip to content

llllllllll/adt

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ADT

Algebraic data types for Python.

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.

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

Struct[_1, _2]

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

List[_1]

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.

Destructuring Types

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

Why?

This is valid python syntax.

About

Algebraic data types for python

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages