Add the Future monad

This commit is contained in:
Correl Roush 2018-12-12 22:50:27 -05:00
parent b08d145da2
commit 93bc193494
5 changed files with 159 additions and 1 deletions

View file

@ -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.

View file

@ -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
View 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

View file

@ -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
View 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)
)