Skip to content

Instantly share code, notes, and snippets.

@malcolmgreaves
Last active May 22, 2019 00:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save malcolmgreaves/1e5c0b0becfd8d94ffc3031a34638391 to your computer and use it in GitHub Desktop.
Save malcolmgreaves/1e5c0b0becfd8d94ffc3031a34638391 to your computer and use it in GitHub Desktop.
A value-or-error disjunction. Inspired by \/ in Scala.
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