from hypothesis import given
import hypothesis.strategies as st
from haskpy.testing import assert_output
from haskpy import testing
from haskpy.internal import class_function
# Use the "hidden" module in order to avoid circular imports
from ._functor import Functor
class Apply(Functor):
"""Apply typeclass
:py:class:`.Apply` is to :py:class:`.Applicative` as :py:class:`.Semigroup`
is to :py:class:`.Monoid`.
Minimal complete definition::
map | (apply | apply_to)
Why do we need :py:class:`.Apply` in addition to :py:class:`.Applicative`?
For instance, :py:class:`.Dictionary` is an instance of :py:class:`.Apply`
but not :py:class:`.Applicative`.
References
----------
- `Apply at PureScript Pursuit
<https://pursuit.purescript.org/packages/purescript-prelude/3.0.0/docs/Control.Apply>`_
"""
[docs] def apply(self, f):
"""m a -> m (a -> b) -> m b
Default implementation is based on ``apply_to``.
"""
return f.apply_to(self)
[docs] def apply_to(self, x):
"""f (a -> b) -> f a -> f b
Default implementation is based on ``apply``.
"""
return x.apply(self)
[docs] def apply_first(self, x):
"""Combine two actions, keeping only the result of the first
::
Apply f => f a -> f b -> f a
"""
from haskpy.utils import const
return self.map(const).apply_to(x)
[docs] def apply_second(self, x):
"""Combine two actions, keeping only the result of the second
::
Apply f => f a -> f b -> f b
"""
from haskpy.utils import identity
return self.replace(identity).apply_to(x)
[docs] def __matmul__(self, x):
"""Application operand ``@`` applies similarly as ``<*>`` in Haskell
``f @ x`` translates to ``f.apply_to(x)``, ``x.apply(f)`` and
``apply(f, x)``.
Why ``@`` operator?
- It's not typically used as often as some other more common operators
so less risk for confusion.
- The operator is not a commutative as isn't ``apply`` either.
- If we see matrix as some structure, then matrix multiplication takes
both left and right operand inside this structure and gives a result
also inside this structure, similarly as ``apply`` does. So it's an
operator for two operands having a similar structure.
- The operator evaluates the contained function(s) at the contained
value(s). Thus, ``f`` "at" ``x`` makes perfect sense.
"""
return self.apply_to(x)
[docs] def __lshift__(self, x):
"""Sequence with``<<`` similarly as with ``<*`` and ``<<`` in Haskell"""
return self.apply_first(x)
[docs] def __rshift__(self, x):
"""Sequence with ``>>`` similarly as with ``*>`` and ``>>`` in Haskell"""
return self.apply_second(x)
#
# Sampling methods for property tests
#
@class_function
def sample_apply_type_constructor(cls):
"""Sample an apply type constructor
By default, :py:meth:`.Functor.sample_functor_type_constructor` is
used. If Apply type requires more constraints than Functor type,
override this default implementation.
"""
return cls.sample_functor_type_constructor()
#
# Test typeclass laws
#
@class_function
@assert_output
def assert_apply_associative_composition(cls, u, v, w):
"""u @ (v @ w) == ((compose ** u) @ v) @ w"""
from haskpy.types.function import compose
return (
u.apply_to(v.apply_to(w)),
u.map(compose).apply_to(v).apply_to(w),
)
@class_function
@given(st.data())
def test_apply_associative_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())
f = data.draw(cls.sample_apply_type_constructor())
fa = f(a)
fab = f(testing.sample_function(b))
fbc = f(testing.sample_function(c))
# Draw values
w = data.draw(fa)
v = data.draw(fab)
u = data.draw(fbc)
cls.assert_apply_associative_composition(u, v, w, data=data)
return
#
# Test laws based on default implementations
#
@class_function
@assert_output
def assert_apply_apply(cls, u, v):
from .apply_ import apply
return (
v.apply(u),
apply(u, v),
u.apply_to(v),
u @ v,
)
@class_function
@given(st.data())
def test_apply_apply(cls, data):
# Draw types
a = data.draw(testing.sample_eq_type())
b = data.draw(testing.sample_type())
f = data.draw(cls.sample_apply_type_constructor())
fa = f(a)
fab = f(testing.sample_function(b))
# Draw values
v = data.draw(fa)
u = data.draw(fab)
cls.assert_apply_apply(u, v, data=data)
return
@class_function
@assert_output
def assert_apply_apply_first(cls, u, v):
from .apply_ import apply_first
return (
Apply.apply_first(u, v),
u.apply_first(v),
apply_first(u, v),
u << v,
)
@class_function
@given(st.data())
def test_apply_apply_first(cls, data):
# Draw types
a = data.draw(testing.sample_type())
b = data.draw(testing.sample_type())
f = data.draw(cls.sample_apply_type_constructor())
fa = f(a)
fb = f(b)
# Draw values
u = data.draw(fa)
v = data.draw(fb)
cls.assert_apply_apply_first(u, v, data=data)
return
@class_function
@assert_output
def assert_apply_apply_second(cls, u, v):
from .apply_ import apply_second
return (
Apply.apply_second(u, v),
u.apply_second(v),
apply_second(u, v),
u >> v,
)
@class_function
@given(st.data())
def test_apply_apply_second(cls, data):
# Draw types
a = data.draw(testing.sample_type())
b = data.draw(testing.sample_type())
f = data.draw(cls.sample_apply_type_constructor())
fa = f(a)
fb = f(b)
# Draw values
u = data.draw(fa)
v = data.draw(fb)
cls.assert_apply_apply_second(u, v, data=data)
return