mirror of
https://github.com/correl/typesafe-monads.git
synced 2024-11-24 03:00:15 +00:00
Add the Future monad
This commit is contained in:
parent
b08d145da2
commit
93bc193494
5 changed files with 159 additions and 1 deletions
|
@ -150,6 +150,12 @@ Represents a sequence of items.
|
|||
|
||||
- Also implements `Monoid`.
|
||||
|
||||
### Future[T]
|
||||
|
||||
Represents an asynchronous action.
|
||||
|
||||
- Also implements `Awaitable`.
|
||||
|
||||
### Reader[T]
|
||||
|
||||
Represents the application of a function to it's argument.
|
||||
|
|
|
@ -4,4 +4,5 @@ from .monad import Monad
|
|||
from .list import List
|
||||
from .maybe import Maybe, Just, Nothing
|
||||
from .result import Result, Ok, Err
|
||||
from .future import Future
|
||||
from .reader import Reader
|
||||
|
|
58
monads/future.py
Normal file
58
monads/future.py
Normal file
|
@ -0,0 +1,58 @@
|
|||
from __future__ import annotations
|
||||
import asyncio
|
||||
from typing import Awaitable, Callable, TypeVar, Union
|
||||
from .monad import Monad
|
||||
|
||||
T = TypeVar("T")
|
||||
S = TypeVar("S")
|
||||
|
||||
|
||||
class Future(Monad[T]):
|
||||
"""Wraps an Awaitable in a Monad.
|
||||
|
||||
The resulting Future object is, itself, Awaitable.
|
||||
"""
|
||||
|
||||
def __init__(self, awaitable: Awaitable[T]) -> None:
|
||||
self.awaitable = awaitable
|
||||
|
||||
@classmethod
|
||||
def pure(cls, value: Union[T, Awaitable[T]]) -> Future[T]:
|
||||
if isinstance(value, Awaitable):
|
||||
return Future(value)
|
||||
else:
|
||||
|
||||
async def identity(x: T) -> T:
|
||||
return x
|
||||
|
||||
return Future(identity(value))
|
||||
|
||||
def map(self, function: Callable[[T], S]) -> Future[S]:
|
||||
async def map(f: Callable[[T], S], x: Awaitable[T]) -> S:
|
||||
x_ = await x
|
||||
return f(x_)
|
||||
|
||||
return Future(map(function, self.awaitable))
|
||||
|
||||
def apply(self, functor: Awaitable[Callable[[T], S]]) -> Future[S]:
|
||||
async def apply(f: Awaitable[Callable[[T], S]], x: Awaitable[T]) -> S:
|
||||
f_ = await f
|
||||
x_ = await x
|
||||
return f_(x_)
|
||||
|
||||
return Future(apply(functor, self.awaitable))
|
||||
|
||||
def bind(self, function: Callable[[T], Awaitable[S]]) -> Future[S]:
|
||||
async def bind(f: Callable[[T], Awaitable[S]], x: Awaitable[T]) -> S:
|
||||
x_ = await x
|
||||
y = await function(x_)
|
||||
return y
|
||||
|
||||
return Future(bind(function, self.awaitable))
|
||||
|
||||
def __await__(self):
|
||||
return self.awaitable.__await__()
|
||||
|
||||
__rshift__ = bind
|
||||
__and__ = lambda other, self: Future.apply(self, other)
|
||||
__mul__ = __rmul__ = map
|
|
@ -1,6 +1,6 @@
|
|||
import pytest # type: ignore
|
||||
from typing import Type
|
||||
from monads import Maybe, List, Result
|
||||
from monads import Maybe, List, Result, Future
|
||||
|
||||
|
||||
@pytest.fixture(scope="module", params=[Maybe, List, Result])
|
||||
|
|
93
tests/test_future.py
Normal file
93
tests/test_future.py
Normal file
|
@ -0,0 +1,93 @@
|
|||
import pytest # type: ignore
|
||||
from typing import Any, Callable, TypeVar
|
||||
|
||||
from monads import Functor, Applicative, Future
|
||||
from monads.reader import curry
|
||||
|
||||
T = TypeVar("T")
|
||||
S = TypeVar("S")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pure_accepts_values_or_awaitables() -> None:
|
||||
async def three() -> int:
|
||||
return 3
|
||||
|
||||
a: Future[int] = Future.pure(3)
|
||||
b: Future[int] = Future.pure(three())
|
||||
assert await a == await b
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_functor_identity() -> None:
|
||||
identity: Callable[[int], int] = lambda x: x
|
||||
assert await Future.pure(3) == await Future.pure(3).map(identity)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_functor_associativity() -> None:
|
||||
f: Callable[[int], int] = lambda x: x + 1
|
||||
g: Callable[[int], str] = lambda x: str(x)
|
||||
assert await Future.pure(3).map(lambda x: g(f(x))) == await Future.pure(3).map(
|
||||
f
|
||||
).map(g)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_functor_map_mul_operator() -> None:
|
||||
identity: Callable[[int], int] = lambda x: x
|
||||
assert await Future.pure(3).map(identity) == await (Future.pure(3) * identity)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_functor_map_rmul_operator() -> None:
|
||||
identity: Callable[[int], int] = lambda x: x
|
||||
assert await Future.pure(3).map(identity) == await (identity * Future.pure(3))
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_applicative_fmap_using_ap() -> None:
|
||||
f: Callable[[int], int] = lambda x: x + 1
|
||||
assert await Future.pure(3).map(f) == await Future.pure(3).apply(Future.pure(f))
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_monad_bind() -> None:
|
||||
expected: Future[int] = Future.pure(2)
|
||||
m: Future[int] = Future.pure(1)
|
||||
assert await expected == await m.bind(lambda x: Future.pure(x + 1))
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_monad_bind_rshift_operator() -> None:
|
||||
f: Callable[[int], Future[int]] = lambda x: Future.pure(x + 1)
|
||||
assert await Future.pure(2).bind(f) == await (Future.pure(2) >> f)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_monad_left_identity() -> None:
|
||||
n: int = 3
|
||||
|
||||
def f(n: int) -> Future[int]:
|
||||
return Future.pure(n * 3)
|
||||
|
||||
m: Future[int] = Future.pure(n)
|
||||
assert await m.bind(f) == await f(n)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_monad_right_identity() -> None:
|
||||
assert await Future.pure(3) == await Future.pure(3).bind(lambda x: Future.pure(x))
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_monad_associativity() -> None:
|
||||
def f(n: int) -> Future[int]:
|
||||
return Future.pure(n * 3)
|
||||
|
||||
def g(n: int) -> Future[int]:
|
||||
return Future.pure(n + 5)
|
||||
|
||||
assert await Future.pure(3).bind(f).bind(g) == await Future.pure(3).bind(
|
||||
lambda x: f(x).bind(g)
|
||||
)
|
Loading…
Reference in a new issue