Permalink
Browse files

Initial work on monads and the Maybe monad

This needs documentation and tests

Signed-off-by: Nicolas Trangez <eikke@eikke.com>
  • Loading branch information...
1 parent b428495 commit 3e9248ec4a065584c01d8224729fffdc72dba11e @NicolasT committed Oct 11, 2009
Showing with 217 additions and 0 deletions.
  1. +71 −0 funpy/maybe.py
  2. +65 −0 funpy/monad.py
  3. +81 −0 test/test_maybe.py
View
@@ -0,0 +1,71 @@
+# funpy, a library for functional programming in Python
+#
+# Copyright (C) 2009 Nicolas Trangez <eikke eikke com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation, version 2.1
+# of the License.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301 USA
+
+from .monad import Monad, NumericMonadMixin
+
+class Maybe(Monad, NumericMonadMixin):
+ __slots__ = tuple()
+
+ return_ = lambda _, value: Just(value)
+ fail = lambda *_: Nothing
+
+ # TODO not implemented raising
+ _stringify = None
+ __str__ = lambda self: self._stringify(str)
+ __repr__ = lambda self: self._stringify(repr)
+ __unicode__ = lambda self: self._stringify(unicode)
+
+
+class Nothing(Maybe):
+ __slots__ = tuple()
+
+ bind = lambda self, _: self
+ __eq__ = lambda self, other: self is other
+
+ _stringify = lambda *_: 'Nothing'
+
+ __hash__ = lambda *_: 9873983764589L
+
+Nothing = Nothing()
+
+
+class Just(Maybe):
+ __slots__ = ('_value', )
+
+ def __init__(self, value):
+ self._value = value
+
+ def bind(self, fun):
+ try:
+ return fun(self._value)
+ except Exception, exc:
+ return self.fail(exc)
+
+ def __eq__(self, other):
+ if not isinstance(other, Maybe):
+ return NotImplemented
+
+ if other is Nothing:
+ return False
+
+ return self._value == other._value
+
+ _stringify = lambda self, fun: 'Just(' + fun(self._value) + ')'
+
+ __hash__ = lambda self: hash(self._value)
View
@@ -0,0 +1,65 @@
+# funpy, a library for functional programming in Python
+#
+# Copyright (C) 2009 Nicolas Trangez <eikke eikke com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation, version 2.1
+# of the License.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301 USA
+
+import operator
+
+class Monad(object):
+ __slots__ = tuple()
+
+ def bind(self, fun):
+ raise NotImplementedError
+
+ def return_(self, value):
+ raise NotImplementedError
+
+ def fail(self, exception):
+ raise NotImplementedError
+
+
+liftM = lambda fun: lambda m: m.bind(lambda x: m.return_(fun(x)))
+
+
+unary_proxy = lambda fun: \
+ lambda self: liftM(lambda x: fun(x))(self)
+binary_proxy = lambda fun: \
+ lambda self, other: liftM(lambda x: fun(x, other))(self)
+
+class NumericMonadMixin:
+ __add__ = binary_proxy(operator.add)
+ __sub__ = binary_proxy(operator.sub)
+ __mul__ = binary_proxy(operator.mul)
+ __floordiv__ = binary_proxy(operator.floordiv)
+ __mod__ = binary_proxy(operator.mod)
+ __divmod__ = binary_proxy(lambda a, b: ((a - (a % b)) / b, a % b))
+ __pow__ = binary_proxy(operator.pow)
+ __lshift__ = binary_proxy(operator.lshift)
+ __rshift__ = binary_proxy(operator.rshift)
+ __and__ = binary_proxy(operator.and_)
+ __xor__ = binary_proxy(operator.xor)
+ __or__ = binary_proxy(operator.or_)
+
+ __div__ = binary_proxy(operator.div)
+ __truediv__ = binary_proxy(operator.truediv)
+
+ __neg__ = unary_proxy(operator.neg)
+ __pos__ = unary_proxy(operator.pos)
+ __abs__ = unary_proxy(operator.abs)
+ __invert__ = unary_proxy(operator.invert)
+
+del unary_proxy, binary_proxy
View
@@ -0,0 +1,81 @@
+# funpy, a library for functional programming in Python
+#
+# Copyright (C) 2009 Nicolas Trangez <eikke eikke com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation, version 2.1
+# of the License.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301 USA
+
+import unittest
+
+from funpy.maybe import Just, Nothing
+
+class TestMonadDiv(unittest.TestCase):
+ '''Test Maybe division, the most obvious example'''
+ def test_correct(self):
+ self.assertEquals(Just(10) / 2, Just(5))
+
+ def test_zero(self):
+ self.assertEquals(Just(10) / 0, Nothing)
+
+ def test_multi_level(self):
+ self.assertEquals(Just(10) / 5 / 2, Just(1))
+ self.assertEquals(Just(10) / 2 / 0 / 2, Nothing)
+
+ def test_nothing(self):
+ self.assertEquals(Nothing / 5, Nothing)
+ self.assertEquals(Nothing / 0, Nothing)
+
+
+class TestSpecialFunctions(unittest.TestCase):
+ def test_str(self):
+ self.assertEquals(str(Just('test')), 'Just(test)')
+
+ def test_repr(self):
+ class T:
+ def __repr__(self): return 'test'
+
+ self.assertEquals(repr(Just(T())), 'Just(test)')
+
+ def test_unicode(self):
+ self.assertEquals(unicode(Just('abc')), u'Just(abc)')
+
+ def test_nothing_str(self):
+ self.assertEquals(str(Nothing), 'Nothing')
+ self.assertEquals(repr(Nothing), 'Nothing')
+ self.assertEquals(unicode(Nothing), u'Nothing')
+
+ def test_just_eq(self):
+ o = object()
+ self.assertEquals(Just(o), Just(o))
+ self.assertNotEquals(Just(o), Just(object))
+ self.assertNotEquals(Just(o), o)
+ self.assertNotEquals(Just(o), Nothing)
+
+ def test_nothing_eq(self):
+ self.assertEquals(Nothing, Nothing)
+ self.assertNotEquals(Nothing, Just(object()))
+ self.assertNotEquals(Nothing, object())
+
+ def test_hash(self):
+ o = object()
+ self.assertEquals(hash(Just(o)), hash(o))
+
+
+class TestNumericFunctions(unittest.TestCase):
+ def test_add(self):
+ self.assertEquals(Just(10) + 5, Just(15))
+ self.assertEquals(Just(10) + Nothing, Nothing)
+ self.assertEquals(Nothing + 10, Nothing)
+ self.assertEquals(Nothing + Nothing, Nothing)

0 comments on commit 3e9248e

Please sign in to comment.