Last active
May 22, 2019 00:30
-
-
Save malcolmgreaves/1e5c0b0becfd8d94ffc3031a34638391 to your computer and use it in GitHub Desktop.
A value-or-error disjunction. Inspired by \/ in Scala.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from typing import TypeVar, Generic, Optional, Callable, cast | |
V = TypeVar("V") | |
E = TypeVar("E") | |
T = TypeVar("T") | |
class ValueOrError(Generic[V, E]): | |
"""A disjunction for holding either a value of type V or an error of type E. | |
""" | |
def __init__(self, value: Optional[V] = None, error: Optional[E] = None) -> None: | |
"""Constructs the disjunction using either the value `V` or error `E` as the internal state. | |
:raises ValueError If construction would result in an ill-formed value-error disjunction. | |
Uses :func:`is_well_formed` to check. | |
""" | |
self._v = value | |
self._e = error | |
if not self.is_well_formed: | |
raise ValueError( | |
"Disjunction is not well-formed. " | |
"Either the value or the error may be present: not both nor neither." | |
f"Value: '{self._v}'' | Error: '{self._e}'" | |
) | |
@property | |
def maybe_value(self) -> Optional[V]: | |
"""The value of type `V`, if present, or `None`. | |
""" | |
return self._v | |
@property | |
def maybe_error(self) -> Optional[E]: | |
"""The error of type `E`, if present, or `None`. | |
""" | |
return self._e | |
@property | |
def value(self) -> V: | |
"""Obatins the value or raises an ValueError if not present. | |
:raises ValueError If no value is present. | |
""" | |
if self._v is not None: | |
return self._v | |
else: | |
raise ValueError("Value is not present.") | |
@property | |
def error(self) -> E: | |
"""Obtains the error or raises a ValueError if not present. | |
:raises ValueError If no error is present. | |
""" | |
if self._e is not None: | |
return self._e | |
else: | |
raise ValueError("Error is not present.") | |
@property | |
def is_value(self) -> bool: | |
"""True iff the value is present. False otherwise. | |
""" | |
return self._v is not None | |
@property | |
def is_error(self) -> bool: | |
"""True iff the error is present. False otherwise. | |
""" | |
return self._e is not None | |
@property | |
def is_well_formed(self) -> bool: | |
"""True if this instance is a well-formed disjunction. False otherwise. | |
This instance is a well-formed disjunction if and only if it either holds a value or it | |
holds error. It is not well-formed if it holds both or neither. | |
""" | |
return (self._e is None and self._v is not None) or ( | |
self._e is not None and self._v is None | |
) | |
def __str__(self) -> str: | |
return f"{type(self)}(value={self.maybe_value}, error={self.maybe_error})" | |
def map_value(f: Callable[[V], T], either: ValueOrError[V, E]) -> ValueOrError[T, E]: | |
"""Transforms only the value of a value-carrying either using the supplied function. | |
""" | |
if either.is_value: | |
new_value = f(either.value) | |
return ValueOrError(new_value) | |
else: | |
return cast(ValueOrError[T, E], either) | |
def map_error(f: Callable[[E], T], either: ValueOrError[V, E]) -> ValueOrError[V, T]: | |
"""Transforms only the error of an error-carrying either using the supplied function. | |
""" | |
if either.is_error: | |
new_error = f(either.error) | |
return ValueOrError(error=new_error) | |
else: | |
return cast(ValueOrError[V, T], either) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment