GenMonads: Python monads with generator-based syntax

Author: Eric Nichols <underspecified@gmail.com>

This module contains python implementations of some scala-style monads.

It provides a generator-based syntax using a decompilation trick from Pony [1] to translate generators into nested calls to a monad’s flat_map(), map(), and filter() functions, in a similar fashion to scala’s for comprehensions [2].

The idea was inspired by a comment by Shin no Noir [3] on a post on A Neighborhood of Infinity [4].

Monad Syntax

GenMonads supports syntax like scala for-comprehensions by using a special function to evaluate a generator over monads (the functions is named mfor(), short for “monadic for comprehension,” as it is modeled after scala’s for comprehensions, but the synonym do() is also available):

>>> from genmonads.monad import mfor
>>> from genmonads.option import *
>>> print(mfor(x + y
               for x in option(2)
               if x < 10
               for y in option(5)
               if y % 2 != 0))
Some(7)

The above generator is automatically translated into the following at run-time:

>>> print(option(2) \
              .filter(lambda x: x < 10) \
              .flat_map(lambda x: option(5) \
                   .filter(lambda y: y % 2 != 0) \
                   .map(lambda y: x + y)))
Some(7)

Both generator expressions and generator functions are supported, though variable assignment in generator function bodies is not currently implemented:

>>> def make_gen():
        for x in option(4):
            if x > 2:
                for y in option(10):
                    if y % 2 == 0:
                        yield x - y
>>> print(mfor(make_gen()))
Some(-6)

Monad chaining with the bind operator is also supported (>>= and >> are combined into a single >> operator due to syntactic limitations in overloading >>= in python):

>>> print(option(5) >> (lambda x: option(x * 2)))
Some(10)
>>> print(option(5) >> (lambda _: option(2)))
Some(2)
>>> print(option(5) >> Nothing())
Nothing

Following scala’s monadic handling of NULL, the option() function can be used to inject computations that can return None into the Option monad:

>>> print(option(None))
Nothing
>>> pets = {'cat': 1, 'dog': 2, 'bird': 3}
>>> print(option(pets.get('dog')))
Some(2)
>>> print(option(pets.get('iguana')))
Nothing

Installation

GenMonads can be installed from the GitHub project page https://github.com/underspecified/GenMonads via pip:

> pip3 install git+https://git@github.com/underspecified/GenMonads.git

Documentation

See the project’s Read the Docs page at https://underspecified.github.io/GenMonads/

Todo

  • variable assignment in generator functions
  • optional Haskell nomenclature
  • Either[A,B] and other monads

License

This project is licensed under the GNU Affero General Public License v3 (AGPLv3).

Indices and tables