Source code for haskpy.typeclasses._functor
from hypothesis import given
from hypothesis import strategies as st
from .typeclass import Type
from haskpy.internal import (
abstract_class_function,
abstract_function,
class_function,
)
from haskpy.testing import assert_output
from haskpy import testing
[docs]class Functor(Type):
"""Covariant functor
Minimal complete definition:
..
map
For property tests:
..
sample_functor_type
Examples
--------
In Haskell, Map is a Functor, but not Applicative
"""
[docs] @abstract_function
def map(self, f):
"""Haskell fmap"""
[docs] def replace(self, x):
"""Haskell ($>) operator"""
return self.map(lambda _: x)
[docs] def __rpow__(self, f):
"""Lifting operator ``**`` lifts similarly as ``<$>`` in Haskell
``f ** x`` translates to ``x.map(f)`` and ``map(f, x)``.
Why ``**`` operator?
- It's not typically used as often as multiplication or addition so
less risk of confusion.
- It's not commutative operator as isn't lifting either.
- The two operands have very different roles. They are not at the same
"level".
- The right operand is "higher", that is, it's inside a structure and
the left operand is kind of "raised to the power" of the second
operand, where the "power" is the functorial structure.
- The same operand is also used for function composition because
function composition is just mapping. Visually the symbol can be
seen as chaining two stars similarly as function composition chains
two functions.
"""
return self.map(f)
#
# Sampling methods for property tests
#
@abstract_class_function
def sample_functor_type(cls, a):
pass
#
# Test typeclass laws
#
@class_function
@assert_output
def assert_functor_identity(cls, v):
from haskpy.utils import identity
return(
v,
v.map(identity),
)
@class_function
@given(st.data())
def test_functor_identity(cls, data):
# Draw types
a = data.draw(testing.sample_type())
fa = data.draw(cls.sample_functor_type(a))
# Draw values
v = data.draw(fa)
cls.assert_functor_identity(v, data=data)
return
@class_function
@assert_output
def assert_functor_composition(cls, v, f, g):
return (
v.map(f).map(g),
v.map(lambda x: g(f(x))),
)
@class_function
@given(st.data())
def test_functor_composition(cls, data):
# Draw types
a = data.draw(testing.sample_eq_type())
b = data.draw(testing.sample_eq_type())
c = data.draw(testing.sample_type())
fa = data.draw(cls.sample_functor_type(a))
# Draw values
v = data.draw(fa)
f = data.draw(testing.sample_function(b))
g = data.draw(testing.sample_function(c))
cls.assert_functor_composition(v, f, g, data=data)
return
#
# Test laws based on default implementations
#
@class_function
@assert_output
def assert_functor_map(cls, v, f):
from .functor import map
return (
v.map(f),
map(f, v),
)
@class_function
@given(st.data())
def test_functor_map(cls, data):
# Draw types
a = data.draw(testing.sample_eq_type())
b = data.draw(testing.sample_type())
fa = data.draw(cls.sample_functor_type(a))
# Draw values
v = data.draw(fa)
f = data.draw(testing.sample_function(b))
cls.assert_functor_map(v, f, data=data)
return
@class_function
@assert_output
def assert_functor_replace(cls, v, x):
from .functor import replace
return (
Functor.replace(v, x),
replace(x, v),
v.replace(x),
)
@class_function
@given(st.data())
def test_functor_replace(cls, data):
# Draw types
a = data.draw(testing.sample_eq_type())
b = data.draw(testing.sample_type())
fa = data.draw(cls.sample_functor_type(a))
# Draw values
v = data.draw(fa)
x = data.draw(b)
cls.assert_functor_replace(v, x, data=data)
return