Ensure the types are correct for all monad methods

This commit is contained in:
Correl Roush 2019-01-03 22:28:41 -05:00
parent 7d63ad0849
commit a340cfea68
10 changed files with 120 additions and 14 deletions

View file

@ -1,6 +1,6 @@
from __future__ import annotations
import asyncio
from typing import Awaitable, Callable, TypeVar, Union
import functools
from typing import Awaitable, Callable, Iterable, List, TypeVar, Union
from .monad import Monad
T = TypeVar("T")
@ -50,6 +50,16 @@ class Future(Monad[T]):
return Future(bind(function, self.awaitable))
@classmethod
def sequence(cls, xs: Iterable[Future[T]]) -> Future[List[T]]:
"""Evaluate monadic actions in sequence, collecting results."""
def mcons(acc: Future[List[T]], x: Future[T]) -> Future[List[T]]:
return acc.bind(lambda acc_: x.map(lambda x_: acc_ + [x_]))
empty: Future[List[T]] = cls.pure([])
return functools.reduce(mcons, xs, empty)
def __await__(self):
return self.awaitable.__await__()

View file

@ -1,7 +1,7 @@
from __future__ import annotations
from functools import reduce
from itertools import chain
from typing import Callable, TypeVar
from typing import Callable, Iterable, List as _List, TypeVar
from .monad import Monad
from .monoid import Monoidal
@ -26,6 +26,16 @@ class List(Monad[T], Monoidal[list]):
list(chain.from_iterable([map(f, self.value) for f in functor.value]))
)
@classmethod
def sequence(cls, xs: Iterable[List[T]]) -> List[_List[T]]:
"""Evaluate monadic actions in sequence, collecting results."""
def mcons(acc: List[_List[T]], x: List[T]) -> List[_List[T]]:
return acc.bind(lambda acc_: x.map(lambda x_: acc_ + [x_]))
empty: List[_List[T]] = cls.pure([])
return reduce(mcons, xs, empty)
@classmethod
def mzero(cls) -> List[T]:
return cls(list())

View file

@ -1,5 +1,6 @@
from __future__ import annotations
from typing import Any, Callable, Generic, List, Optional, TypeVar
import functools
from typing import Any, Callable, Generic, Iterable, List, Optional, TypeVar
from .monad import Monad
from .monoid import Monoid
@ -37,6 +38,16 @@ class Maybe(Monad[T]):
new: Maybe[S] = Nothing()
return new
@classmethod
def sequence(cls, xs: Iterable[Maybe[T]]) -> Maybe[List[T]]:
"""Evaluate monadic actions in sequence, collecting results."""
def mcons(acc: Maybe[List[T]], x: Maybe[T]) -> Maybe[List[T]]:
return acc.bind(lambda acc_: x.map(lambda x_: acc_ + [x_]))
empty: Maybe[List[T]] = cls.pure([])
return functools.reduce(mcons, xs, empty)
def withDefault(self, default: T) -> T:
if isinstance(self, Just):
return self.value

View file

@ -21,13 +21,13 @@ class Monad(Applicative[T]):
def pure(cls, value: T) -> Monad[T]: # pragma: no cover
raise NotImplementedError
# FIXME: Iterable type set to Any, as the proper value (Monad[T])
# is reported as incompatible with subclass implementations due to
# a flaw in mypy: https://github.com/python/mypy/issues/1317
@classmethod
def sequence(cls, xs: Iterable[Monad[T]]) -> Monad[List[T]]:
def sequence(cls, xs: Iterable[Any]) -> Monad[List[T]]: # pragma: no cover
"""Evaluate monadic actions in sequence, collecting results."""
def reducer(acc: Monad[List[T]], x: Monad[T]) -> Monad[List[T]]:
return acc.bind(lambda acc_: x.map(lambda x_: acc_ + [x_]))
return functools.reduce(reducer, xs, cls.pure([]))
raise NotImplementedError
__rshift__ = bind

View file

@ -1,6 +1,6 @@
from __future__ import annotations
from functools import reduce, update_wrapper
from typing import Any, Callable, Generic, TypeVar
from typing import Any, Callable, Generic, Iterable, List, TypeVar
from .monad import Monad
@ -35,6 +35,16 @@ class Reader(Monad[T], Generic[Env, T]):
f: Callable[[Env], S] = lambda x: function(self.function(x))(x)
return Reader(f)
@classmethod
def sequence(cls, xs: Iterable[Reader[Env, T]]) -> Reader[Env, List[T]]:
"""Evaluate monadic actions in sequence, collecting results."""
def mcons(acc: Reader[Env, List[T]], x: Reader[Env, T]) -> Reader[Env, List[T]]:
return acc.bind(lambda acc_: x.map(lambda x_: acc_ + [x_]))
empty: Reader[Env, List[T]] = cls.pure([])
return reduce(mcons, xs, empty)
def __eq__(self, other: object): # pragma: no cover
return isinstance(other, Reader) and self.function == other.function
@ -45,6 +55,7 @@ class Reader(Monad[T], Generic[Env, T]):
__mul__ = __rmul__ = map
__rshift__ = bind
__and__ = lambda other, self: Reader.apply(self, other)
class Curried(Reader[Env, T]):

View file

@ -1,5 +1,6 @@
from __future__ import annotations
from typing import Any, Callable, Generic, TypeVar
import functools
from typing import Any, Callable, Generic, Iterable, List, TypeVar
from .monad import Monad
@ -51,6 +52,16 @@ class Result(Monad[T], Generic[T, E]):
else: # pragma: no cover
raise TypeError
@classmethod
def sequence(cls, xs: Iterable[Result[T, E]]) -> Maybe[List[T]]:
"""Evaluate monadic actions in sequence, collecting results."""
def mcons(acc: Result[List[T], E], x: Result[T, E]) -> Result[List[T], E]:
return acc.bind(lambda acc_: x.map(lambda x_: acc_ + [x_]))
empty: Result[List[T], E] = cls.pure([])
return functools.reduce(mcons, xs, empty)
def withDefault(self, default: T) -> T:
if isinstance(self, Ok):
return self.value

View file

@ -1,5 +1,5 @@
import pytest # type: ignore
from typing import Any, Callable, TypeVar
from typing import Any, Callable, List, TypeVar
from monads import Functor, Applicative, Future
from monads.reader import curry
@ -8,6 +8,26 @@ T = TypeVar("T")
S = TypeVar("S")
@pytest.mark.asyncio
async def test_types() -> None:
m: Future[int] = Future.pure(1)
await m
map: Future[int] = Future.pure(1).map(lambda x: x)
await map
map_operator: Future[int] = Future.pure(1) * (lambda x: x)
await map_operator
bind: Future[int] = Future.pure(1).bind(lambda x: Future.pure(x))
await bind
bind_operator: Future[int] = Future.pure(1) >> (lambda x: Future.pure(x))
await bind_operator
apply: Future[int] = Future.pure(1).apply(Future.pure(lambda x: x))
await apply
apply_operator: Future[int] = Future.pure(lambda x: x) & Future.pure(1)
await apply_operator
sequence: Future[List[int]] = Future.sequence([Future.pure(1)])
await sequence
@pytest.mark.asyncio
async def test_pure_accepts_values_or_awaitables() -> None:
async def three() -> int:

View file

@ -5,6 +5,17 @@ from monads.maybe import Maybe, Just, Nothing, maybe, first, last
from monads.result import Ok, Err
def test_types() -> None:
m: Maybe[int] = Maybe.pure(1)
map: Maybe[int] = m.map(lambda x: x)
map_operator: Maybe[int] = m * (lambda x: x)
bind: Maybe[int] = m.bind(lambda x: Maybe.pure(x))
bind_operator: Maybe[int] = m >> (lambda x: Maybe.pure(x))
apply: Maybe[int] = m.apply(Maybe.pure(lambda x: x))
apply_operator: Maybe[int] = Maybe.pure(lambda x: x) & m
sequence: Maybe[List[int]] = Maybe.sequence([m])
def test_bind_just() -> None:
m: Maybe[int] = Just(5)
increment: Callable[[int], Maybe[int]] = lambda x: Just(x + 1)

View file

@ -1,5 +1,5 @@
import pytest # type: ignore
from typing import Any, Callable, TypeVar
from typing import Any, Callable, List, TypeVar
from monads import Functor, Applicative, Reader
from monads.reader import curry
@ -8,6 +8,17 @@ T = TypeVar("T")
S = TypeVar("S")
def test_types() -> None:
m: Reader[Any, int] = Reader.pure(1)
map: Reader[Any, int] = m.map(lambda x: x)
map_operator: Reader[Any, int] = m * (lambda x: x)
bind: Reader[Any, int] = m.bind(lambda x: Reader.pure(x))
bind_operator: Reader[Any, int] = m >> (lambda x: Reader.pure(x))
apply: Reader[Any, int] = m.apply(Reader.pure(lambda x: x))
apply_operator: Reader[Any, int] = Reader.pure(lambda x: x) & m
sequence: Reader[Any, List[int]] = Reader.sequence([m])
def test_functor_identity() -> None:
m: Reader = Reader.pure(3)
identity: Callable[[T], T] = lambda x: x

View file

@ -1,10 +1,21 @@
from __future__ import annotations
from typing import Callable
from typing import Callable, List
from monads.maybe import Maybe, Just, Nothing
from monads.result import Result, Ok, Err, safe
def test_types() -> None:
m: Result[int, str] = Result.pure(1)
map: Result[int, str] = m.map(lambda x: x)
map_operator: Result[int, str] = m * (lambda x: x)
bind: Result[int, str] = m.bind(lambda x: Result.pure(x))
bind_operator: Result[int, str] = m >> (lambda x: Result.pure(x))
apply: Result[int, str] = m.apply(Result.pure(lambda x: x))
apply_operator: Result[int, str] = Result.pure(lambda x: x) & m
sequence: Result[List[int], str] = Result.sequence([m])
def test_bind_ok() -> None:
m: Result[int, str] = Ok(5)
increment: Callable[[int], Result[int, str]] = lambda x: Ok(x + 1)