Skip to content

Instantly share code, notes, and snippets.

@ddjerqq
Created October 22, 2022 23:37
Show Gist options
  • Save ddjerqq/c7233519225a6ff6b27e527c3415b344 to your computer and use it in GitHub Desktop.
Save ddjerqq/c7233519225a6ff6b27e527c3415b344 to your computer and use it in GitHub Desktop.
rust option and result but in python
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