mirror of
https://github.com/correl/typesafe-monads.git
synced 2024-11-24 11:09:58 +00:00
Ensure the types are correct for all monad methods
This commit is contained in:
parent
7d63ad0849
commit
a340cfea68
10 changed files with 120 additions and 14 deletions
|
@ -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__()
|
||||
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue