Source code for haskpy.types.maybe

"""Optional values

Types:

.. autosummary::
   :toctree:

    Maybe

Data constructors:

.. autosummary::
   :toctree:

    Nothing
    Just

Monad transformers:

.. autosummary::
   :toctree:

    MaybeT

"""

import attr
import hypothesis.strategies as st

from haskpy.typeclasses import (
    Monad,
    Commutative,
    Monoid,
    Hashable,
    Foldable,
    Eq,
)
from haskpy.utils import (
    immutable,
    class_function,
    class_property,
    eq_test,
)

from haskpy import testing


[docs]@immutable class Maybe( Monad, Commutative, Monoid, Hashable, Foldable, Eq, ): """Maybe type for optional values""" match = attr.ib()
[docs] @class_property def empty(cls): return Nothing
[docs] @class_function def pure(cls, x): return Just(x)
[docs] def map(self, f): return self.match( Nothing=lambda: Nothing, Just=lambda x: Just(f(x)), )
[docs] def apply_to(self, x): return self.match( Nothing=lambda: Nothing, Just=lambda f: x.map(f), )
[docs] def bind(self, f): return self.match( Nothing=lambda: Nothing, Just=lambda x: f(x), )
[docs] def append(self, m): return self.match( Nothing=lambda: m, Just=lambda x: m.match( Nothing=lambda: self, Just=lambda y: Just(x.append(y)), ), )
[docs] def fold_map(self, monoid, f): return self.match( Nothing=lambda: monoid.empty, Just=lambda x: f(x), )
[docs] def foldl(self, combine, initial): return self.match( Nothing=lambda: initial, Just=lambda x: combine(initial)(x), )
[docs] def foldr(self, combine, initial): return self.match( Nothing=lambda: initial, Just=lambda x: combine(x)(initial), )
[docs] def length(self): return self.match( Nothing=lambda: 0, Just=lambda _: 1, )
[docs] def to_iter(self): yield from self.match( Nothing=lambda: (), Just=lambda x: (x,), )
def __repr__(self): return self.match( Nothing=lambda: "Nothing", Just=lambda x: "Just({0})".format(repr(x)), )
[docs] def __eq__(self, other): return self.match( Nothing=lambda: other.match( Nothing=lambda: True, Just=lambda _: False, ), Just=lambda x: other.match( Nothing=lambda: False, Just=lambda y: x == y, ), )
[docs] def __eq_test__(self, other, data): return self.match( Nothing=lambda: other.match( Nothing=lambda: True, Just=lambda _: False, ), Just=lambda x: other.match( Nothing=lambda: False, Just=lambda y: eq_test(x, y, data), ), )
# # Sampling methods for property tests # @class_function def sample_value(cls, a): return st.one_of(st.just(Nothing), a.map(Just)) sample_type = testing.sample_type_from_value( testing.sample_type(), ) sample_functor_type = testing.sample_type_from_value() sample_applicative_type = sample_functor_type sample_monad_type = sample_functor_type sample_hashable_type = testing.sample_type_from_value( testing.sample_hashable_type(), ) sample_semigroup_type = testing.sample_type_from_value( testing.sample_semigroup_type(), ) sample_monoid_type = sample_semigroup_type sample_commutative_type = testing.sample_type_from_value( testing.sample_commutative_type(), ) sample_eq_type = testing.sample_type_from_value( testing.sample_commutative_type(), ) sample_foldable_type = testing.sample_type_from_value() sample_foldable_functor_type = sample_foldable_type
Nothing = Maybe(lambda *, Nothing, Just: Nothing()) """Nada"""
[docs]def Just(x): """Just do it""" return Maybe(lambda *, Nothing, Just: Just(x))
[docs]def MaybeT(M): """m (Maybe a) -> MaybeT m a""" # NOTE: It might be tempting to use Compose as a basis for this kind of # Monad transformers. Indeed, it seems like these are just monadic # extensions to the composition that can be written only for Applicatives # in generic form. However, that is not the case. See an example in # test_maybe.py on how MaybeT isn't a monadic extension of Compose but # rather its Applicative instance is different from that of Compose. This # is rather interesting as it's a common misconception that the common # monad transformers are monadic extensions of Compose. Even "Learn Haskell # programming from first principles" introduces monad transformers in such # a way that it leads to that kind of understanding. class MetaMaybeM(type(Monad)): def __repr__(cls): return "MaybeT({})".format(repr(M)) @immutable class MaybeM(Monad, Eq, metaclass=MetaMaybeM): # The attribute name may sound weird but it makes sense once you # understand that this indeed is the not-yet-composed variable and if # you want to decompose a composed variable you get it by x.decomposed. # Thus, there's no need to write a simple function to just return this # attribute, just use this directly. decomposed = attr.ib() @class_function def pure(cls, x): """a -> MaybeT m a""" return cls(M.pure(Maybe.pure(x))) def bind(self, f): """MaybeT m a -> (a -> MaybeT m b) -> MaybeT m b In decompressed terms: m (Maybe a) -> (a -> m (Maybe b)) -> m (Maybe b) """ # :: m (Maybe a) mMa = self.decomposed # :: Maybe a -> m (Maybe b) def g(Ma): return Ma.match( Nothing=lambda: M.pure(Nothing), Just=lambda x: f(x).decomposed, ) # :: MaybeT m b return MaybeM(mMa.bind(g)) def __eq__(self, other): return self.decomposed == other.decomposed def __repr__(self): return "{0}({1})".format(repr(MaybeM), repr(self.decomposed)) def __eq_test__(self, other, data): return eq_test(self.decomposed, other.decomposed, data) @class_function def sample_value(cls, a): return M.sample_value(Maybe.sample_value(a)).map(cls) sample_type = testing.sample_type_from_value( testing.sample_type(), ) sample_functor_type = testing.sample_type_from_value() sample_applicative_type = sample_functor_type sample_monad_type = sample_functor_type sample_eq_type = testing.sample_type_from_value( testing.sample_eq_type(), ) return MaybeM