<NME> test_maybe.py
<BEF> # coding=utf-8
import unittest

from oslash.maybe import Maybe, Just, Nothing


class TestMaybeFunctor(unittest.TestCase):
    def test_just_fmap(self):
        b = Just(21).fmap(lambda x: x*2)
        self.assertEquals(b, Just(42))

    def test_nothing_fmap(self):
        a = Nothing().fmap(lambda x: x+2)
        self.assertEquals(a, Nothing())

    def test_nothing_functor_law1(self):
        """fmap id = id"""
        _id = lambda x: x
        self.assertEquals(Nothing().fmap(_id), Nothing())

    def test_just_functor_law1(self):
        """fmap id = id"""
        _id = lambda x: x
        self.assertEquals(Just(3).fmap(_id), Just(3))

    def test_just_functor_law2(self):
        """fmap (f . g) x = fmap f (fmap g x)"""
        def f(x):
            return x+10

        def g(x):
            return x*10

        self.assertEquals(
            Just(42).fmap(f).fmap(g),
            Just(42).fmap(lambda x: g(f(x)))
        )

    def test_nothing_functor_law2(self):
        """fmap (f . g) x = fmap f (fmap g x)"""
        def f(x):
            return x+10

        def g(x):
            return x*10

        self.assertEquals(
            Nothing().fmap(g).fmap(f),
            Nothing().fmap(lambda x: g(f(x)))
        )


class TestMaybeApplicative(unittest.TestCase):

    def test_just_applicative_1(self):
        a = Just.pure(lambda x, y: x+y).apply(Just(2)).apply(Just(40))
        self.assertNotEquals(a, Nothing())
        self.assertEqual(a, Just(42))

    def test_just_applicative_2(self):
        a = Just.pure(lambda x, y: x+y).apply(Nothing()).apply(Just(42))
        self.assertEqual(a, Nothing())

    def test_just_applicative_3(self):
        a = Just.pure(lambda x, y: x+y).apply(Just(42)).apply(Nothing())
        self.assertEqual(a, Nothing())


class TestMaybeMonoid(unittest.TestCase):

    def test_maybe_monoid_nothing_append_just(self):
        m = Just("Python")

        self.assertEqual(
            Nothing() + m,
            m
        )

    def test_maybe_monoid_just_append_nothing(self):
        m = Just("Python")

        self.assertEqual(
            m + Nothing(),
            m
        )


class TestMaybeMonad(unittest.TestCase):

    def test_maybe_monad_bind(self):
        m = Just(42).bind(lambda x: Just(x*10))
        self.assertEqual(m, Just(420))

    def test_maybe_monad_nothing_bind(self):
        """Nothing >>= \\x -> return (x*10)"""
        m = Nothing().bind(lambda x: Just(x*10))
        self.assertEqual(m, Nothing())

    def test_maybe_monad_law_left_identity(self):
        # return 3 >>= (\x -> Just (x+100000))
        a = Just(3).bind(lambda x: Just(x+100000))
        # (\x -> Just (x+100000)) 3
        b = (lambda x: Just(x+100000))(3)
        self.assertEqual(a, b)

    def test_maybe_monad_law_right_identity(self):
        # Just "move on up" >>= (\x -> return x)
        a = Just("move on up").bind(Just.return_)
        self.assertEqual(a, Just("move on up"))

    def test_maybe_monad_law_associativity(self):
        # (m >>= f) >>= g is just like doing m >>= (\x -> f x >>= g)
        a = Just(42).bind(lambda x: Just(x+1000)).bind(lambda y: Just(y*100))
        b = Just(42).bind(lambda x: Just(x+1000).bind(lambda y: Just(y*100)))
        self.assertEqual(a, b)

        self.assertEqual(
            m.bind(f).bind(g),
            m.bind(lambda x: f(x).bind(g))
        )

    def test_nothing_monad_law_associativity(self):
        # (m >>= f) >>= g is just like doing m >>= (\x -> f x >>= g)
        m = Nothing()
        f = lambda x: unit(x+1000)
        g = lambda y: unit(y*42)

        self.assertEqual(
            m.bind(f).bind(g),
            m.bind(lambda x: f(x).bind(g))
        )

    def test_combine_just_and_just_rule1(self):
        self.assertEqual(
            Just(5) and Just(6),
            Just(6)
        )

    def test_combine_just_and_just_rule2(self):
        self.assertEqual(
            Just(0) and Just(6),
            Just(0)
        )

    def test_combine_just_or_nothing_rule1(self):
        self.assertEqual(
            Just(5) or Nothing,
            Just(5)
        )

    def test_combine_just_or_nothing_rule2(self):
        self.assertEqual(
            Just(0) or Nothing,
            Nothing
        )

<MSG> Refactored tests

<DFF> @@ -2,56 +2,181 @@
 import unittest
 
 from oslash.maybe import Maybe, Just, Nothing
+from oslash.util import identity, compose, compose2
+
+pure = Just.pure
+return_ = Just.return_
 
 
 class TestMaybeFunctor(unittest.TestCase):
-    def test_just_fmap(self):
-        b = Just(21).fmap(lambda x: x*2)
-        self.assertEquals(b, Just(42))
+    def test_just_functor_fmap(self):
+        f = lambda x: x*2
+        x = Just(21)
 
-    def test_nothing_fmap(self):
-        a = Nothing().fmap(lambda x: x+2)
-        self.assertEquals(a, Nothing())
+        self.assertEquals(
+            x.fmap(f),
+            Just(42)
+        )
+
+    def test_nothing_functor_fmap(self):
+        f = lambda x: x+2
+        x = Nothing()
+
+        self.assertEquals(
+            x.fmap(f),
+            x
+        )
 
     def test_nothing_functor_law1(self):
-        """fmap id = id"""
-        _id = lambda x: x
-        self.assertEquals(Nothing().fmap(_id), Nothing())
+        # fmap id = id
+        self.assertEquals(
+            Nothing().fmap(identity),
+            Nothing()
+        )
 
     def test_just_functor_law1(self):
-        """fmap id = id"""
-        _id = lambda x: x
-        self.assertEquals(Just(3).fmap(_id), Just(3))
+        # fmap id = id
+        x = Just(3)
+        self.assertEquals(
+            x.fmap(identity),
+            x
+        )
 
     def test_just_functor_law2(self):
-        """fmap (f . g) x = fmap f (fmap g x)"""
+        # fmap (f . g) x = fmap f (fmap g x)
         def f(x):
             return x+10
 
         def g(x):
             return x*10
 
+        x = Just(42)
+
         self.assertEquals(
-            Just(42).fmap(f).fmap(g),
-            Just(42).fmap(lambda x: g(f(x)))
+            x.fmap(compose(f, g)),
+            x.fmap(g).fmap(f)
         )
 
     def test_nothing_functor_law2(self):
-        """fmap (f . g) x = fmap f (fmap g x)"""
+        # fmap (f . g) x = fmap f (fmap g x)
         def f(x):
             return x+10
 
         def g(x):
             return x*10
 
+        x = Nothing()
+
         self.assertEquals(
-            Nothing().fmap(g).fmap(f),
-            Nothing().fmap(lambda x: g(f(x)))
+            x.fmap(compose(f, g)),
+            x.fmap(g).fmap(f)
         )
 
 
 class TestMaybeApplicative(unittest.TestCase):
 
+    def test_just_applicative_law_functor(self):
+        # pure f <*> x = fmap f x
+        x = return_(42)
+        f = lambda x: x * 42
+
+        self.assertEquals(
+            pure(f).apply(x),
+            x.fmap(f)
+        )
+
+    def test_nothing_applicative_law_functor(self):
+        # pure f <*> x = fmap f x
+        x = Nothing()
+        f = lambda x: x * 42
+
+        self.assertEquals(
+            pure(f).apply(x),
+            x.fmap(f)
+        )
+
+    def test_just_applicative_law_identity(self):
+        # pure id <*> v = v
+        v = return_(42)
+
+        self.assertEquals(
+            pure(identity).apply(v),
+            v
+        )
+
+    def test_nothing_applicative_law_identity(self):
+        # pure id <*> v = v
+        v = Nothing()
+
+        self.assertEquals(
+            pure(identity).apply(v),
+            v
+        )
+
+    def test_just_applicative_law_composition(self):
+        # pure (.) <*> u <*> v <*> w = u <*> (v <*> w)
+
+        w = return_(42)
+        u = pure(lambda x: x * 42)
+        v = pure(lambda x: x + 42)
+
+        self.assertEquals(
+            pure(compose2).apply(u).apply(v).apply(w),
+            u.apply(v.apply(w))
+        )
+
+    def test_identity_applicative_law_composition(self):
+        # pure (.) <*> u <*> v <*> w = u <*> (v <*> w)
+
+        w = Nothing()
+        u = pure(lambda x: x * 42)
+        v = pure(lambda x: x + 42)
+
+        self.assertEquals(
+            pure(compose2).apply(u).apply(v).apply(w),
+            u.apply(v.apply(w))
+        )
+
+    def test_just_applicative_law_homomorphism(self):
+        # pure f <*> pure x = pure (f x)
+        x = 42
+        f = lambda x: x * 42
+
+        self.assertEquals(
+            pure(f).apply(pure(x)),
+            pure(f(x))
+        )
+
+    def test_nothing_applicative_law_homomorphism(self):
+        # pure f <*> pure x = pure (f x)
+        f = lambda x: x * 42
+
+        self.assertEquals(
+            pure(f).apply(Nothing()),
+            Nothing()
+        )
+
+    def test_just_applicative_law_interchange(self):
+        # u <*> pure y = pure ($ y) <*> u
+
+        y = 43
+        u = return_(lambda x: x*42)
+
+        self.assertEquals(
+            u.apply(pure(y)),
+            pure(lambda f: f(y)).apply(u)
+        )
+
+    def test_nothing_applicative_law_interchange(self):
+        # u <*> pure y = pure ($ y) <*> u
+
+        u = return_(lambda x: x*42)
+
+        self.assertEquals(
+            u.apply(Nothing()),
+            Nothing().apply(u)
+        )
+
     def test_just_applicative_1(self):
         a = Just.pure(lambda x, y: x+y).apply(Just(2)).apply(Just(40))
         self.assertNotEquals(a, Nothing())
@@ -87,29 +212,83 @@ class TestMaybeMonoid(unittest.TestCase):
 
 class TestMaybeMonad(unittest.TestCase):
 
-    def test_maybe_monad_bind(self):
-        m = Just(42).bind(lambda x: Just(x*10))
-        self.assertEqual(m, Just(420))
+    def test_just_monad_bind(self):
+        m = return_(42)
+        f = lambda x: return_(x*10)
+
+        self.assertEqual(
+            m.bind(f),
+            return_(420)
+        )
 
-    def test_maybe_monad_nothing_bind(self):
-        """Nothing >>= \\x -> return (x*10)"""
-        m = Nothing().bind(lambda x: Just(x*10))
-        self.assertEqual(m, Nothing())
+    def test_nothing_monad_bind(self):
+        m = Nothing()
+        f = lambda x: return_(x*10)
+
+        self.assertEqual(
+            m.bind(f),
+            Nothing()
+        )
 
-    def test_maybe_monad_law_left_identity(self):
-        # return 3 >>= (\x -> Just (x+100000))
-        a = Just(3).bind(lambda x: Just(x+100000))
-        # (\x -> Just (x+100000)) 3
-        b = (lambda x: Just(x+100000))(3)
-        self.assertEqual(a, b)
+    def test_just_monad_law_left_identity(self):
+        # return x >>= f is the same thing as f x
 
-    def test_maybe_monad_law_right_identity(self):
-        # Just "move on up" >>= (\x -> return x)
-        a = Just("move on up").bind(Just.return_)
-        self.assertEqual(a, Just("move on up"))
+        f = lambda x: return_(x+100000)
+        x = 3
 
-    def test_maybe_monad_law_associativity(self):
+        self.assertEqual(
+            return_(x).bind(f),
+            f(x)
+        )
+
+    def test_nothing_monad_law_left_identity(self):
+        # return x >>= f is the same thing as f x
+
+        f = lambda x: return_(x+100000)
+
+        self.assertEqual(
+            Nothing().bind(f),
+            Nothing()
+        )
+
+    def test_just_monad_law_right_identity(self):
+        # m >>= return is no different than just m.
+
+        m = return_("move on up")
+
+        self.assertEqual(
+            m.bind(return_),
+            m
+        )
+
+    def test_nothing_monad_law_right_identity(self):
+        # m >>= return is no different than just m.
+
+        m = Nothing()
+
+        self.assertEqual(
+            m.bind(return_),
+            m
+        )
+
+    def test_just_monad_law_associativity(self):
+        # (m >>= f) >>= g is just like doing m >>= (\x -> f x >>= g)
+        m = return_(42)
+        f = lambda x: return_(x+1000)
+        g = lambda y: return_(y*42)
+
+        self.assertEqual(
+            m.bind(f).bind(g),
+            m.bind(lambda x: f(x).bind(g))
+        )
+
+    def test_nothing_monad_law_associativity(self):
         # (m >>= f) >>= g is just like doing m >>= (\x -> f x >>= g)
-        a = Just(42).bind(lambda x: Just(x+1000)).bind(lambda y: Just(y*100))
-        b = Just(42).bind(lambda x: Just(x+1000).bind(lambda y: Just(y*100)))
-        self.assertEqual(a, b)
+        m = Nothing()
+        f = lambda x: return_(x+1000)
+        g = lambda y: return_(y*42)
+
+        self.assertEqual(
+            m.bind(f).bind(g),
+            m.bind(lambda x: f(x).bind(g))
+        )

Refactored tests