typesafe-monads/monads/result.py

123 lines
3.5 KiB
Python
Raw Normal View History

2018-10-11 05:26:00 +00:00
from __future__ import annotations
import functools
from typing import Any, Callable, Generic, Iterable, List, TypeVar
2018-10-11 05:26:00 +00:00
from . import maybe
from .monad import Monad
2018-10-11 05:26:00 +00:00
T = TypeVar("T")
S = TypeVar("S")
E = TypeVar("E")
class Result(Monad[T], Generic[T, E]):
def __init__(self) -> None: # pragma: no cover
2018-10-11 05:26:00 +00:00
raise NotImplementedError
@classmethod
def pure(cls, value: T) -> Result[T, E]:
2018-10-11 05:26:00 +00:00
return Ok(value)
def bind(self, function: Callable[[T], Result[S, E]]) -> Result[S, E]:
if isinstance(self, Ok):
return function(self.value)
elif isinstance(self, Err):
new: Result[S, E] = Err(self.err)
return new
else: # pragma: no cover
2018-10-11 05:26:00 +00:00
raise TypeError
def map(self, function: Callable[[T], S]) -> Result[S, E]:
2018-10-11 05:26:00 +00:00
if isinstance(self, Ok):
return Result.pure(function(self.value))
2018-10-11 05:26:00 +00:00
elif isinstance(self, Err):
new: Result[S, E] = Err(self.err)
return new
else: # pragma: no cover
2018-10-11 05:26:00 +00:00
raise TypeError
2018-12-12 03:19:13 +00:00
def mapError(self, function: Callable[[E], S]) -> Result[T, S]:
if isinstance(self, Err):
return Err(function(self.err))
elif isinstance(self, Ok):
return Ok(self.value)
else: # pragma: no cover
raise TypeError
2018-10-12 18:09:16 +00:00
def apply(self, functor: Result[Callable[[T], S], E]) -> Result[S, E]:
if isinstance(functor, Ok):
return self.map(functor.value)
elif isinstance(functor, Err):
new: Result[S, E] = Err(functor.err)
return new
else: # pragma: no cover
2018-10-12 18:09:16 +00:00
raise TypeError
@classmethod
def sequence(cls, xs: Iterable[Result[T, E]]) -> Result[List[T], E]:
"""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)
2018-10-11 05:26:00 +00:00
def withDefault(self, default: T) -> T:
if isinstance(self, Ok):
return self.value
else:
return default
2018-12-03 21:16:04 +00:00
@classmethod
def fromMaybe(cls, m: maybe.Maybe[T], error: E) -> Result[T, E]:
2018-12-03 21:16:04 +00:00
return m.map(Result.pure).withDefault(Err(error))
def toMaybe(self) -> maybe.Maybe[T]:
return self.map(maybe.Maybe.pure).withDefault(maybe.Nothing())
2018-12-03 21:16:04 +00:00
2018-10-11 05:26:00 +00:00
__rshift__ = bind
__and__ = lambda other, self: Result.apply(self, other)
__mul__ = __rmul__ = map
2018-10-11 05:26:00 +00:00
class Ok(Result[T, E]):
def __init__(self, value: T) -> None:
self.value = value
def __eq__(self, other: object):
return isinstance(other, Ok) and self.value == other.value
def __repr__(self) -> str: # pragma: no cover
2018-10-11 05:26:00 +00:00
return f"<Ok {self.value}>"
class Err(Result[T, E]):
def __init__(self, err: E) -> None:
self.err = err
def __eq__(self, other: object):
return isinstance(other, Err) and self.err == other.err
def __repr__(self) -> str: # pragma: no cover
2018-10-11 05:26:00 +00:00
return f"<Err {self.err}>"
def safe(function: Callable[..., T]) -> Callable[..., Result[T, Exception]]:
"""Wraps a function that may raise an exception.
e.g.:
@safe
def bad() -> int:
raise Exception("oops")
"""
def wrapped(*args, **kwargs) -> Result[T, Exception]:
try:
return Ok(function(*args, **kwargs))
except Exception as e:
return Err(e)
return wrapped