Source code for haskpy.typeclasses._bind
import hypothesis.strategies as st
from hypothesis import given
from haskpy.internal import class_function
from haskpy.testing import assert_output
from haskpy import testing
# Use the "hidden" module in order to avoid circular imports
from ._apply import Apply
class Bind(Apply):
    """Bind typeclass extends :py:class:`.Apply` with a bind operation
    Minimal complete definition::
        map & (bind | join)
    Typeclass laws:
    - Associativity: ``(x % f) % g = x % (lambda k: f(k) % g)``
    """
[docs]    def bind(self, f):
        """m a -> (a -> m b) -> m b
        Default implementation is based on ``join`` and ``map``:
        self :: m a
        f :: a -> m b
        map f :: m a -> m (m b)
        join :: m (m b) -> m b
        """
        return self.map(f).join() 
[docs]    def join(self):
        """m (m a) -> m a
        Default implementation is based on ``bind``:
        self :: m (m a)
        identity :: m a -> m a
        bind :: m (m a) -> (m a -> m a) -> m a
        """
        from haskpy.utils import identity
        return self.bind(identity) 
[docs]    def apply(self, f):
        r"""m a -> m (a -> b) -> m b
          self :: m a
          f :: m (a -> b)
        Default implementation is based on ``bind`` and ``map``. In order to
        use ``bind``, let's write its type as follows:
          bind :: m (a -> b) -> ((a -> b) -> m b) -> m b
        Let's also use a simple helper function:
          h = \g -> map g self :: (a -> b) -> m b
        Now:
          bind f h :: m b
        """
        return f.bind(lambda g: self.map(g)) 
[docs]    def __mod__(self, f):
        """Use ``%`` as bind operator similarly as ``>>=`` in Haskell
        That is, ``x % f`` is equivalent to ``bind(x, f)`` and ``x.bind(f)``.
        Why ``%`` operator?
        - It's not very often used so less risk for confusion.
        - It's not commutative as isn't bind either.
        - It is similar to bind in a sense that the result has the same unit as
          the left operand while the right operand has different unit.
        - The symbol works visually as a line "binds" two circles and on the
          other hand two circles tell about two similar structures on both
          sides but those structures are just on different "level".
        """
        return self.bind(f) 
    #
    # Sampling methods for property tests
    #
    @class_function
    def sample_bind_type_constructor(cls):
        """Sample type constructor of Bind instance
        By default, :py:meth:`.Apply.sample_apply_type_constructor` is used. If
        Bind type requires more constraints than pply type, override this
        default implementation.
        """
        return cls.sample_apply_type_constructor()
    #
    # Test typeclass laws
    #
    @class_function
    @assert_output
    def assert_bind_associativity(cls, m, f, g):
        return (
            m.bind(f).bind(g),
            m.bind(lambda x: f(x).bind(g)),
        )
    @class_function
    @given(st.data())
    def test_bind_associativity(cls, data):
        a = data.draw(testing.sample_eq_type())
        b = data.draw(testing.sample_eq_type())
        c = data.draw(testing.sample_type())
        m = data.draw(cls.sample_bind_type_constructor())
        ma = m(a)
        mb = m(b)
        mc = m(c)
        m = data.draw(ma)
        f = data.draw(testing.sample_function(mb))
        g = data.draw(testing.sample_function(mc))
        cls.assert_bind_associativity(m, f, g, data=data)
        return
    #
    # Test laws based on default implementations
    #
    @class_function
    @assert_output
    def assert_bind_bind(cls, u, f):
        from .bind_ import bind
        return (
            Bind.bind(u, f),
            u.bind(f),
            bind(u, f),
        )
    @class_function
    @given(st.data())
    def test_bind_bind(cls, data):
        """Test consistency of ``bind`` with the default implementation"""
        # Draw types
        a = data.draw(testing.sample_eq_type())
        b = data.draw(testing.sample_type())
        m = data.draw(cls.sample_bind_type_constructor())
        ma = m(a)
        mb = m(b)
        # Draw values
        u = data.draw(ma)
        f = data.draw(testing.sample_function(mb))
        cls.assert_bind_bind(u, f, data=data)
        return
    @class_function
    @assert_output
    def assert_bind_join(cls, u):
        from .bind_ import join
        return (
            Bind.join(u),
            u.join(),
            join(u),
        )
    @class_function
    @given(st.data())
    def test_bind_join(cls, data):
        """Test consistency of ``join`` with the default implementation"""
        # Draw types
        b = data.draw(testing.sample_type())
        m = data.draw(cls.sample_bind_type_constructor())
        mb = m(b)
        mmb = m(mb)
        # Draw values
        u = data.draw(mmb)
        cls.assert_bind_join(u, data=data)
        return
    @class_function
    @assert_output
    def assert_bind_apply(cls, u, v):
        return (
            Bind.apply(v, u),
            v.apply(u),
        )
    @class_function
    @given(st.data())
    def test_bind_apply(cls, data):
        """Test consistency ``apply`` with the default implementations"""
        # Draw types
        a = data.draw(testing.sample_eq_type())
        b = data.draw(testing.sample_type())
        m = data.draw(cls.sample_bind_type_constructor())
        ma = m(a)
        mab = m(testing.sample_function(b))
        # Draw values
        v = data.draw(ma)
        u = data.draw(mab)
        cls.assert_bind_apply(u, v, data=data)
        return