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