Created
October 22, 2022 23:37
-
-
Save ddjerqq/c7233519225a6ff6b27e527c3415b344 to your computer and use it in GitHub Desktop.
rust option and result but in python
This file contains hidden or 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 __future__ import annotations | |
from typing import Any | |
from typing import Callable | |
from typing import Generic | |
from typing import Tuple | |
from typing import TypeVar | |
from typing import Union | |
from typing import overload | |
T = TypeVar("T") | |
U = TypeVar("U") | |
V = TypeVar("V") | |
E = TypeVar("E") | |
R = TypeVar("R", bound="Result") | |
class Option(Generic[T]): | |
"""Optional values. | |
Type :class:`Option` represents an optional value: every :class:`Option` | |
is either :class:`Some` and contains a value, or :class:`None`, and | |
does not. :class:`Option` types are very common in ~Rust~ Python code, as | |
they have a number of uses: | |
* Initial values | |
* Return values for functions that are not defined over their entire input range (partial functions) | |
* Return value for otherwise reporting simple errors, where :class:`None` is returned on error | |
* Optional struct fields | |
* Struct fields that can be loaned or "taken" | |
* Optional function arguments | |
* Nullable pointers | |
* Swapping things out of difficult situations | |
create using :class:`Option.ome`], :class:`Option.one`] or :class:`Option.rom_`]. | |
""" | |
def __init__(self, value: T | None = None) -> None: | |
"""create an :class:`Option` from an optional value. | |
:param value: optional value | |
:return: :class:`Option` | |
""" | |
self.__value = value | |
def is_some(self) -> bool: | |
"""Check if the option is :class:`Some`. | |
:return: `True` if the option is a :class:`Some` value. | |
""" | |
return self.__value is not None | |
def is_some_and(self: Option[T], func: Callable[[T], bool]) -> bool: | |
"""Check if the option is :class:`Some` and the value satisfies the given predicate. | |
:param func: predicate | |
:return: `True` if the option is a :class:`Some` value and the value satisfies the given predicate. | |
""" | |
return func(self.__value) if self.__value is not None else False | |
def is_none(self) -> bool: | |
"""Check if the option is :class:`None`. | |
:return: `True` if the option is a :class:`None` value. | |
""" | |
return self.__value is None | |
def expect(self: Option[T], msg: str) -> T: | |
"""Returns the contained :class:`Some` value, consuming the `self` value. | |
:param msg: error message | |
:return: contained value if :class:`Some` | |
:raises: :class:`Exception` with the message if :class:`None` | |
""" | |
if self.__value is not None: | |
return self.__value | |
else: | |
raise Exception(msg) | |
def unwrap(self: Option[T]) -> T: | |
"""Returns the contained :class:`Some` value, consuming the `self` value. | |
:return: contained value if :class:`Some` | |
:raises: :class:`Exception` if :class:`None` | |
""" | |
if self.__value is not None: | |
return self.__value | |
else: | |
raise Exception("called `Option.unwrap()` on a `None` value") | |
def unwrap_or(self: Option[T], default: T) -> T: | |
"""Returns the contained :class:`Some` value or a provided default. | |
:param default: default value if :class:`None` | |
:return: contained value if :class:`Some` or default value | |
""" | |
if self.__value is not None: | |
return self.__value | |
else: | |
return default | |
def unwrap_or_else(self: Option[T], func: Callable[[], T]) -> T: | |
"""Returns the contained :class:`Some` value or computes it from a closure. | |
:param func: closure which computes a default value | |
:return: contained value if :class:`Some` or computed value | |
""" | |
if self.__value is not None: | |
return self.__value | |
else: | |
return func() | |
def map(self: Option[T], func: Callable[[T], U]) -> Option[U]: | |
"""Maps an :class:`Option[T]` to :class:`Option[U]` by applying a function to a contained value. | |
:param func: function to apply | |
:return: :class:`Option[U]` | |
""" | |
if self.__value is not None: | |
return Option(func(self.__value)) | |
else: | |
return Option() | |
def inspect(self: Option[T], func: Callable[[T], None]) -> Option[T]: | |
"""Inspect the value of the option. | |
if it is :class:`Some`, the given function is applied to the contained value. | |
:param func: function to apply | |
:return: :class:`Option[T]` | |
""" | |
if self.__value is not None: | |
func(self.__value) | |
return self | |
def map_or(self: Option[T], default: U, func: Callable[[T], U]) -> U: | |
"""Maps an :class:`Option[T]` to :class:`U` by applying a function to a contained value. | |
or returns the provided default. | |
:param default: default value if :class:`None` | |
:param func: function to apply | |
:return: :class:`U` | |
""" | |
if self.__value is not None: | |
return func(self.__value) | |
else: | |
return default | |
def map_or_else(self: Option[T], default: Callable[[], U], func: Callable[[T], U]) -> U: | |
"""Maps an :class:`Option[T]` to :class:`U` by applying a function to a contained value. | |
or computes a default value. | |
:param default: closure which computes a default value | |
:param func: function to apply | |
:return: :class:`U` | |
""" | |
if self.__value is not None: | |
return func(self.__value) | |
else: | |
return default() | |
def ok_or(self: Option[T], err: E) -> Result[T, E]: | |
"""Converts from :class:`Option[T]` to :class:`Result[T, E]`. | |
:param err: any kind of error value. often an Exception | |
:return: :class:`Result[T, E]` | |
""" | |
if self.__value is not None: | |
return Result.ok(self.__value) | |
else: | |
return Result.err(err) | |
def ok_or_else(self: Option[T], func: Callable[[], E]) -> Result[T, E]: | |
"""Converts from :class:`Option[T]` to :class:`Result[T, E]`, mapping :class:`None` to a provided error. | |
:param func: closure which computes an error value | |
:return: :class:`Result[T, E]` | |
""" | |
if self.__value is not None: | |
return Result.ok(self.__value) | |
else: | |
return Result.err(func()) | |
def and_(self: Option[T], option: Option[U]) -> Option[U]: | |
"""Returns `option` if the option is :class:`Some`, otherwise returns :class:`None`. | |
:param option: :class:`Option[U]` | |
:return: :class:`Option[U]` | |
""" | |
if self.__value is not None: | |
return option | |
else: | |
return Option() | |
def __and__(self: Option[U], other: Option[U]) -> Option[U]: | |
return self.and_(other) | |
def and_then(self: Option[T], func: Callable[[T], Option[U]]) -> Option[U]: | |
"""Calls `func` if the option is :class:`Some`, otherwise returns :class:`None`. | |
:param func: function to apply | |
:return: :class:`Option[U]` | |
""" | |
if self.__value is not None: | |
return func(self.__value) | |
else: | |
return Option() | |
def filter(self: Option[T], func: Callable[[T], bool]) -> Option[T]: | |
"""Returns :class:`None` if the option is :class:`None`, otherwise calls `func` with the wrapped value and returns | |
:param func: function to apply | |
:return: :class:`Option[T]` | |
""" | |
if self.__value is not None and func(self.__value): | |
return self | |
else: | |
return Option() | |
def or_(self: Option[T], option: Option[T]) -> Option[T]: | |
"""Returns the option if it contains a value, otherwise returns `option`. | |
:param option: :class:`Option[T]` | |
:return: :class:`Option[T]` | |
""" | |
if self.__value is not None: | |
return self | |
else: | |
return option | |
def __or__(self: Option[T], other: Option[T]) -> Option[T]: | |
return self.or_(other) | |
def or_else(self: Option[T], func: Callable[[], Option[T]]) -> Option[T]: | |
"""Returns the option if it contains a value, otherwise calls `func` and returns the result. | |
:param func: closure which computes an :class:`Option[T]` | |
:return: :class:`Option[T]` | |
""" | |
if self.__value is not None: | |
return self | |
else: | |
return func() | |
def xor(self: Option[T], option: Option[T]) -> Option[T]: | |
"""Returns :class:`None` if both options are :class:`None` or if both options are :class:`Some` with the same value. | |
:param option: :class:`Option[T]` | |
:return: :class:`Option[T]` | |
""" | |
if self.__value is not None: | |
if option.is_none(): | |
return self | |
else: | |
return Option() | |
else: | |
return option | |
def __xor__(self: Option[T], other: Option[T]) -> Option[T]: | |
return self.xor(other) | |
def take(self: Option[T]) -> Option[T]: | |
"""Takes the value out of the option if it is :class:`Some`, leaving a :class:`None` in its place. | |
:return: :class:`Option[T]` | |
""" | |
if self.__value is not None: | |
value = self.__value | |
self.__value = None | |
return Option(value) | |
else: | |
return Option() | |
def replace(self: Option[T], value: T) -> Option[T]: | |
"""Replaces the value of the option if it is :class:`Some`, leaving a :class:`None` in its place. | |
:param value: value to replace with | |
:return: :class:`Option[T]` | |
""" | |
if self.__value is not None: | |
old_value = self.__value | |
self.__value = value | |
return Option(old_value) | |
else: | |
return Option() | |
def zip(self: Option[T], option: Option[U]) -> Option[Tuple[T, U]]: | |
"""Zips the two options together into a single option of a tuple. | |
:param option: :class:`Option[U]` | |
:return: :class:`Option[Tuple[T, U]]` | |
""" | |
if self.__value is not None and option.is_some(): | |
return Option((self.__value, option.unwrap())) | |
else: | |
return Option() | |
def zip_with(self, option: Option[U], func: Callable[[T, U], V]) -> Option[V]: | |
"""Zips the two options together into a single option of a tuple. Then applies the provided function to the tuple. | |
:param option: :class:`Option[U]` | |
:param func: function to apply to the tuple | |
:return: :class:`Option[V]` | |
""" | |
if self.__value is not None and option.is_some(): | |
return Option(func(self.__value, option.unwrap())) | |
else: | |
return Option() | |
def transpose(self: Option[Result[T, E]]) -> Result[Option[T], E]: | |
"""Transposes an :class:`Option[Result[T, E]]` into a :class:`Result[Option[T], E]`. | |
:return: :class:`Result[Option[T], E]` | |
""" | |
if self.__value is not None: | |
if self.__value.is_ok(): | |
return Result.ok(Option(self.__value.unwrap())) | |
else: | |
return Result.err(self.__value.unwrap_err()) | |
else: | |
return Result.ok(Option()) | |
class Result(Generic[T, E]): | |
"""Error handling with the :class:`Result[T, E]` type. | |
:class:`Result[T, E]` is the type used for returning and propagating errors. | |
It is an enum with the variants, :class:`Ok(T)`, representing success and containing a value, | |
and :class:`Err(E)`, representing error and containing an error value. | |
""" | |
def __init__(self, is_ok: bool, ok_or_err: Union[T, E]) -> None: | |
self.__is_ok = is_ok | |
self.__value = ok_or_err | |
@classmethod | |
def from_(cls: Result[T, E], f: Callable[[Any], T], *args, **kwargs) -> Result[T, E]: | |
"""Creates a :class:`Result[T, E]` by calling the function with the arguments and keyword arguments. | |
capturing the return value in :class:`Ok(T)` or any exceptions in :class:`Err(E)`. | |
:param f: function to call | |
:param args: arguments to pass to the function | |
:param kwargs: keyword arguments to pass to the function | |
:return: :class:`Result[T, E]` | |
""" | |
try: | |
return Result.ok(f(*args, **kwargs)) | |
except Exception as e: | |
return Result.err(e) | |
@classmethod | |
@overload | |
def ok(cls, value: T) -> Result[T, E]: | |
"""Creates a :class:`Result[T, E]` from a value. | |
:param value: value to wrap in :class:`Ok(T)` | |
:return: :class:`Result[T, E]` | |
""" | |
@overload | |
def ok(self) -> Option[T]: | |
"""Returns the value from a :class:`Result[T, E]` if it is :class:`Ok(T)` discarding the error if any. | |
:return: :class:`Option[T]` | |
""" | |
def ok(self: Result[T, E] | None = None, value: T | None = None) -> Result[T, E] | Option[T]: | |
if self is None: | |
# we are calling class.ok | |
if value is None: | |
raise TypeError("Result.ok(...) requires a value") | |
return Result(True, value) | |
else: | |
# we are calling instance.ok | |
from option import Option | |
if self.__is_ok: | |
return Option.some(self.__value) | |
else: | |
return Option.none() | |
@classmethod | |
@overload | |
def err(cls, value: E) -> Result[T, E]: | |
"""Creates a :class:`Result[T, E]` from an error. | |
:param value: error to wrap in :class:`Err(E)` | |
:return: :class:`Result[T, E]` | |
""" | |
@overload | |
def err(self: Result): | |
"""Returns the error from a :class:`Result[T, E]` if it is :class:`Err(E)` discarding the value if any. | |
:return: :class:`Option[E]` | |
""" | |
def err(self: Result[T, E] | None, value: E | None = None) -> Result[T, E] | Option[E]: | |
if self is None: | |
# we are calling Result.err | |
return Result(False, value) | |
else: | |
# we are calling result.err | |
from option import Option | |
if self.__is_ok: | |
return Option.none() | |
else: | |
return Option.some(self.__value) | |
def is_ok(self) -> bool: | |
"""Returns true if the result is :class:`Ok(T)`. | |
:return: bool | |
""" | |
return self.__is_ok | |
def is_ok_and(self: Result[T, E], func: Callable[[T], bool]) -> bool: | |
"""Returns true if the result is :class:`Ok(T)` and the provided function returns true when applied to the value. | |
:param func: function to apply to the value | |
:return: bool | |
""" | |
if self.__is_ok: | |
return func(self.__value) | |
else: | |
return False | |
def is_err(self: Result[T, E]) -> bool: | |
"""Returns true if the result is :class:`Err(E)`. | |
:return: bool | |
""" | |
return not self.__is_ok | |
def is_err_and(self: Result[T, E], func: Callable[[E], bool]) -> bool: | |
"""Returns true if the result is :class:`Err(E)` and the provided function returns true when applied to the error. | |
:param func: function to apply to the error | |
:return: bool | |
""" | |
if self.__is_ok: | |
return False | |
else: | |
return func(self.__value) | |
def map(self: Result[T, E], func: Callable[[T], U]) -> Result[U, E]: | |
"""Maps a :class:`Result[T, E]` to :class:`Result[U, E]` by applying a function to a contained :class:`Ok(T)` value, leaving an :class:`Err(E)` value untouched. | |
:param func: function to apply to the value | |
:return: :class:`Result[U, E]` | |
""" | |
if self.__is_ok: | |
return Result.ok(func(self.__value)) | |
else: | |
return Result.err(self.__value) | |
def map_or(self: Result[T, E], default: U, func: Callable[[T], U]) -> U: | |
"""Maps a :class:`Result[T, E]` to U by applying a function to a contained :class:`Ok(T)` value, or a fallback function to a contained :class:`Err(E)` value. | |
:param default: fallback function to apply to the error | |
:param func: function to apply to the value | |
:return: U | |
""" | |
if self.__is_ok: | |
return func(self.__value) | |
else: | |
return default(self.__value) | |
def map_or_else(self: Result[T, E], default: Callable[[E], U], func: Callable[[T], U]) -> U: | |
"""Maps a :class:`Result[T, E]` to U by applying a function to a contained :class:`Ok(T)` value, or a fallback function to a contained :class:`Err(E)` value. | |
:param default: fallback function to apply to the error | |
:param func: function to apply to the value | |
:return: U | |
""" | |
if self.__is_ok: | |
return func(self.__value) | |
else: | |
return default(self.__value) | |
def map_err(self: Result[T, E], func: Callable[[E], V]) -> Result[T, V]: | |
"""Maps a :class:`Result[T, E]` to :class:`Result[T, V]` by applying a function to a contained :class:`Err(E)` value, leaving an :class:`Ok(T)` value untouched. | |
:param func: function to apply to the error | |
:return: :class:`Result[T, V]` | |
""" | |
if self.__is_ok: | |
return Result.ok(self.__value) | |
else: | |
return Result.err(func(self.__value)) | |
def inspect(self: Result[T, E], func: Callable[[T], None]) -> Result[T, E]: | |
"""Inspect a :class:`Result[T, E]` by applying a function to a contained :class:`Ok(T)` value, leaving an :class:`Err(E)` value untouched. | |
:param func: function used to inspect the value | |
:return: :class:`Result[T, E]`, self untouched | |
""" | |
if self.__is_ok: | |
func(self.__value) | |
return self | |
def inspect_err(self: Result[T, E], func: Callable[[E], None]) -> Result[T, E]: | |
"""Inspect a :class:`Result[T, E]` by applying a function to a contained :class:`Err(E)` value, leaving an :class:`Ok(T)` value untouched. | |
:param func: function used to inspect the error | |
:return: :class:`Result[T, E]`, self untouched | |
""" | |
if not self.__is_ok: | |
func(self.__value) | |
return self | |
def expect(self: Result[T, E], msg: str) -> T: | |
"""Unwraps a :class:`Result[T, E]`, yielding the content of an :class:`Ok(T)` or raising an exception if the value is :class:`Err(E)`. | |
:param msg: message to use in the exception | |
:return: :class:`T` | |
:raises: Exception if the value is :class:`Err(E)` and if :class:`E` is instance of Exception, the exception is raised from the value | |
""" | |
if self.__is_ok: | |
return self.__value | |
else: | |
if isinstance(self.__value, Exception): | |
raise Exception(msg) from self.__value | |
else: | |
raise Exception(msg) | |
def unwrap(self: Result[T, E]) -> T: | |
"""Unwraps a :class:`Result[T, E]`, yielding the content of an :class:`Ok(T)`. | |
:return: :class:`T` | |
:raises: Exception if the value is :class:`Err(E)` | |
""" | |
if self.__is_ok: | |
return self.__value | |
else: | |
if isinstance(self.__value, Exception): | |
raise Exception("called `Result::unwrap` on an `Err` value") from self.__value | |
else: | |
raise Exception("called `Result::unwrap` on an `Err` value") | |
def and_(self: Result[T, E], res: Result[U, E]) -> Result[U, E]: | |
"""Returns res if the result is :class:`Ok(T)`, otherwise returns the :class:`Err(E)` value of self. | |
:param res: :class:`Result[U, E]` to return | |
:return: :class:`Result[U, E]` | |
""" | |
if self.__is_ok: | |
return res | |
else: | |
return Result.err(self.__value) | |
def __and__(self: Result[T, E], other: Result[T, E]): | |
return self.and_(other) | |
def and_then(self: Result[T, E], func: Callable[[T], Result[U, E]]) -> Result[U, E]: | |
"""Calls func if the result is :class:`Ok(T)`, otherwise returns the :class:`Err(E)` value of self. | |
:param func: | |
:return: | |
""" | |
if self.is_ok(): | |
return func(self.__value) | |
else: | |
return Result.err(self.__value) | |
def or_(self: Result[T, E], res: Result[T, V]) -> Result[T, V]: | |
"""Returns res if the result is :class:`Err(E)`, otherwise returns the :class:`Ok(T)` value of self. | |
:param res: :class:`Result[T, V]` to return | |
:return: :class:`Result[T, V]` | |
""" | |
if self.__is_ok: | |
return Result.ok(self.__value) | |
else: | |
return res | |
def __or__(self: Result[T, E], other: Result[T, E]): | |
return self.or_(other) | |
def or_else(self: Result[T, E], func: Callable[[E], Result[T, V]]) -> Result[T, V]: | |
"""Calls func if the result is :class:`Err(E)`, otherwise returns the :class:`Ok(T)` value of self. | |
:param func: function to call if the result is :class:`Err(E)` | |
:return: :class:`Result[T, V]` | |
""" | |
if self.__is_ok: | |
return Result.ok(self.__value) | |
else: | |
return func(self.__value) | |
def unwrap_or(self: Result[T, E], default: T) -> T: | |
"""Unwraps a result, yielding the content of an :class:`Ok(T)`. Else it returns the default. | |
:param default: default value to return if the result is :class:`Err(E)` | |
:return: :class:`T` | |
""" | |
if self.__is_ok: | |
return self.__value | |
else: | |
return default | |
def unwrap_or_else(self: Result[T, E], func: Callable[[E], T]) -> T: | |
"""Unwraps a result, yielding the content of an :class:`Ok(T)`. Else it calls func with the error value and returns the result. | |
:param func: function to call if the result is :class:`Err(E)` | |
:return: :class:`T` | |
""" | |
if self.__is_ok: | |
return self.__value | |
else: | |
return func(self.__value) | |
def transpose(self: Result[Option[T], E]) -> Option[Result[T, E]]: | |
"""Transposes a :class:`Result[Option[T], E]` into an :class:`Option[Result[T, E]]`. | |
:return: :class:`Option[Result[T, E]]` | |
""" | |
if self.__is_ok: | |
if self.__value.is_some(): | |
return Option(Result.ok(self.__value.unwrap())) | |
else: | |
return Option() | |
else: | |
return Option(Result.err(self.__value)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment