Skip to content

Instantly share code, notes, and snippets.

@hcarvalhoalves
Created July 15, 2015 14:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hcarvalhoalves/c270b6bda326eccc7cfe to your computer and use it in GitHub Desktop.
Save hcarvalhoalves/c270b6bda326eccc7cfe to your computer and use it in GitHub Desktop.
from __future__ import unicode_literals, print_function, generators
import doctest
# TODO:
# - 'do' notation
# - Py3 type annotations
# - More monadic types
class FormattedException(Exception):
def __init__(self, **kwargs):
self.kwargs = kwargs
def __str__(self):
return self.tmpl.format(**self.kwargs)
class IllegalTransformation(FormattedException):
tmpl = """
Expected function of type {expected}
Returned type was {actual}
"""
class NonExhaustivePatternMatching(FormattedException):
tmpl = """
No pattern for type {expected} found
"""
class Monad(object):
def __repr__(self):
return "{}()".format(self.__class__.__name__)
def concrete(self):
"""
Get the concrete subclass from the method resolution order.
>>> class MyImp(Monad):
... pass
>>> MyImp().concrete().__name__
'MyImp'
"""
mro = self.__class__.mro()
return mro[mro.index(Monad) - 1]
def lift(self, f):
"""
Lifts a function of type (a -> b) to (a -> M b).
"""
return self.__lift__(f)
def bind(self, f):
"""
Composes a function of type (a -> M b). Asserts the function
type matches by checking return type at runtime.
>>> class MyImp(Monad):
... def __bind__(self, f):
... return f(42)
...
>>> MyImp().bind(lambda x: MyImp())
MyImp()
>>> try:
... MyImp().bind(lambda x: x + 2)
... except Exception as e:
... isinstance(e, IllegalTransformation)
True
"""
r = self.__bind__(f)
expected = self.concrete()
if not isinstance(r, expected):
raise IllegalTransformation(expected=expected, actual=type(r))
return r
def caseof(self, matches):
"""
Pattern match based on a mapping of types to functions.
>>> class MyImp(Monad):
... def __caseof__(self, f):
... return f(21)
...
>>> class OtherType(object):
... pass
>>> MyImp().caseof({
... MyImp: lambda x: x * 2,
... OtherType: lambda x: x / 0})
42
"""
try:
f = matches[type(self)]
except KeyError:
raise NonExhaustivePatternMatching(expected=type(self))
return self.__caseof__(f)
class Maybe(Monad):
def __repr__(self):
"""
>>> m = Maybe()
>>> print(m)
Maybe()
>>> m.value = 'foo'
>>> print(m)
Maybe(u'foo')
"""
boxed = repr(self.value) if hasattr(self, 'value') else ''
return "{}({})".format(self.__class__.__name__, boxed)
class Just(Maybe):
def __init__(self, value):
"""
>>> Just(5)
Just(5)
"""
self.value = value
def __eq__(self, other):
"""
>>> Just(5) == Just(5)
True
>>> Just(5) == Just(42)
False
"""
return self.value == other
def __lift__(self, f):
"""
>>> Just(5).lift(lambda x: x * 5)
Just(25)
"""
return Just(f(self.value))
def __bind__(self, f):
"""
>>> Just(5).bind(lambda x: Just(x * 5))
Just(25)
>>> try:
... Just(5).bind(lambda x: "Something else")
... except Exception as e:
... isinstance(e, IllegalTransformation)
True
"""
return f(self.value)
def __caseof__(self, f):
"""
>>> Just(5).caseof({
... Just: lambda x: x * 5,
... Nothing: 10})
25
"""
return self.__bind__(f)
class Nothing(Maybe):
def __init__(self):
"""
>>> Nothing()
Nothing()
"""
pass
def __eq__(self, other):
"""
>>> Nothing() == Nothing()
True
"""
return isinstance(other, Nothing)
def __lift__(self, f):
"""
>>> Nothing().lift(lambda x: x * 5)
Nothing()
"""
return Nothing()
def __bind__(self, f):
"""
>>> Just(5).bind(lambda x: Nothing()).lift(lambda x: x * 5)
Nothing()
>>> Nothing().bind(lambda x: "Something else")
Nothing()
"""
return Nothing()
def __caseof__(self, f):
"""
>>> Nothing().caseof({
... Just: lambda x: x * 5,
... Nothing: lambda: 0})
0
"""
return f()
def maybe(v):
return Just(v)
if __name__ == '__main__':
doctest.testmod(verbose=False, report=True)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment