apfel.container.maybe
A container that optionally holds a value.
A Maybe has two possible states, Just or Nothing. Just means a value is present, and Nothing means the value is absent.
The reason that we don’t use Some-None or Nil nomenclature is to avoid confusion with the built-in None.
This module also provides a some constructor, which converts an Optional[T] value to a Maybe[T] value.
The Maybe class, and the just, nothing, and some functions
are exposed in the package namespace.
Rationale
Python has a built-in None object that represents the absence of a value,
and a corresponding type hint Optional[T].
However, an Optional[T] is semantically different from a Maybe[T] object.
An Optional[T] is a union of T and None, not a single object.
To use a union, you must explicitly check its type before every use.
# Usage 1if optional is None: ...else: do_something(optional)
# Usage 2do_something(optional) if optional is not None else ...
# Usage 3optional is not None and do_something(optional)On the other hand, a Maybe[T] object represents the absence of a value as a state.
Operations defined on Maybe[T] objects behave differently depending on the state of the object, but they are always available regardless of the state.
j: Maybe[int] = just(42)j.map(do_something) # `do_something` is called because `j` is `Just`.
n: Maybe[int] = nothing()n.map(do_something) # `do_something` is not called because `n` is `Nothing`.Implementation
Maybe implements the following interfaces:
| Interface | Methods |
|---|---|
Functor |
map |
Applicative |
pure, apply |
Monad |
bind(and_then) |
The Maybe class is a generic class that holds a value of type T inside _val field.
A boolean flag _has_value is used to indicate whether the value is present or absent.
If _has_value is False, reading from _val is an undefined behavior.
Maybe and its methods do not support inherit-based subclassing.
Maybe’s APIs are based on the Rust Option,
and the comparison table is provided below.
Reference Option |
Counterpart |
|---|---|
and |
and_ |
and_then |
and_then |
as_deref |
/ |
as_deref_mut |
/ |
as_mut |
/ |
as_mut_slice |
/ |
as_pin_mut |
/ |
as_pin_ref |
/ |
as_ref |
/ |
as_slice |
/ |
cloned |
- |
copied |
/ |
expect |
expect |
filter |
filter |
flatten |
~flatten |
get_or_insert |
get_or_insert |
get_or_insert_default |
/ |
get_or_insert_with |
get_or_insert_with |
insert |
insert |
inspect |
~tap |
is_none |
is_nothing |
is_some |
is_just |
is_some_and |
is_just_and |
iter |
- |
iter_mut |
/ |
map |
map |
map_or |
map_or |
map_or_else |
map_or_else |
ok_or |
ok_or |
ok_or_else |
ok_or_else |
or |
or_ |
or_else |
or_else |
replace |
replace |
take |
take |
take_if |
take_if |
transpose |
- |
unwrap |
unwrap |
unwrap_or |
unwrap_or |
unwrap_or_default |
/ |
unwrap_or_else |
unwrap_or_else |
unwrap_unchecked |
unwrap_unchecked |
unzip |
/ |
xor |
xor |
zip |
~zip |
zip_with |
/ |
API#
Maybeclass#
class Maybe(val: T = ..., /, *, has_value: Literal[False] = True)Bases: Monad
A container that optionally holds a value. See module-level documentation for more information.
This class is exposed in the package namespace.
__slots__attributeclass attributeinstance attribute#
__slots__: tuple[str, str] = ('_val', '_has_value')__and__method#
def __and__(other: Maybe[U], /) -> Maybe[U]__bool__method#
def __bool__() -> boolEquivalent to is_just.
__class_getitem__method#
def __class_getitem__(item) -> Maybe[U]__eq__method#
def __eq__(other: Any, /) -> boolIf both values are Just, compare the inner values. Otherwise, return True if both are Nothing.
__hash__method#
def __hash__() -> int__init__method#
def __init__(val: T = ..., /, *, has_value: Literal[False] = True)__len__method#
def __len__() -> intReturn 1 if the value is Just, otherwise 0.
__or__method#
def __or__(other: Maybe[T], /) -> Maybe[T]__repr__method#
def __repr__() -> str__str__method#
def __str__() -> strand_method#
def and_(other: Maybe[U], /) -> Maybe[U]If the value is Just, return a shallow copy of the other Maybe. Otherwise, return a new Nothing of type Maybe[U].
j1 = just[int](42)j2 = just[bool](True)
assert j1.and_(j2).unwrap() == Trueassert (j1 & j2).unwrap() == Trueassert (j1 & nothing[bool]()).is_nothing()assert (nothing[int]() & j2).is_nothing()and_thenmethod#
def and_then(func: Callable[[T], Maybe[U]], /) -> Maybe[U]If the value is Just, apply a function that maps the inner value to a Maybe[U] value. Otherwise, return Nothing of type Maybe[U].
j = just[int](114514)
assert j.and_then(lambda x: some(x + 1805296)).unwrap() == 1919810applymethod#
def apply(func: Maybe[Callable[[T], U]], /) -> Maybe[U]Implementation of Applicative.apply.
Applies the callable wrapped within a Maybe to the inner value, if both are Just.
bindmethod#
def bind(func: Callable[[T], Maybe[U]], /) -> Maybe[U]Implementation of Monad.bind.
Alias of and_then.
duplicatemethodclassmethod#
def duplicate(value: Maybe[T], /) -> Maybe[T]Create a shallow copy of a Maybe value.
expectmethod#
def expect(message: str, /) -> TUnwrap the inner value, if any. Otherwise, raise a ValueError with a custom message.
Raises
ValueError- If the value is a
Nothing.
filtermethod#
def filter(pred: Callable[[T], bool], /) -> Maybe[T]If the value is Just and satisfies the predicate, return the value. Otherwise, return Nothing.
j = just[int](42)assert j.filter(lambda x: x > 0).unwrap() == 42assert j.filter(lambda x: x < 0).is_nothing()flattenmethod#
def flatten() -> Maybe[T]Flatten a nested Maybe value for one level.
Unlike Option::flatten, this method does not require the inner value to be a Maybe.
If it’s not a nested Maybe, this is a no-op.
j = just[Maybe[int]](just(42))assert j.flatten() == just(42)
j2 = just[Maybe[Maybe[int]]](just(just(42)))assert j2.flatten() == just(just(42))assert j2.flatten().flatten() == just(42)get_or_insertmethod#
def get_or_insert(default: T, /) -> TGet the inner value, if any. Otherwise, insert the new value and return the value.
j = just[int](42)assert j.get_or_insert(114514) == 42
n = nothing[int]()assert n.get_or_insert(114514) == 114514get_or_insert_withmethod#
def get_or_insert_with(func: Callable[[], T], /) -> TGet the inner value, if any. Otherwise, call a function to get a new value and return the value.
j = just[int](42)assert j.get_or_insert_with(lambda: 114514) == 42
n = nothing[int]()assert n.get_or_insert_with(lambda: 114514) == 114514insertmethod#
def insert(value: T, /) -> TInsert a value and returns it.
is_justmethod#
def is_just() -> boolCheck if the value is a Just.
is_just_andmethod#
def is_just_and(pred: Callable[[T], bool], /) -> boolCheck if the value is a Just and satisfies the predicate.
is_nothingmethod#
def is_nothing() -> boolCheck if the value is a Nothing.
make_justmethodclassmethod#
def make_just(value: T, /) -> SelfConstruct a Just value.
make_nothingmethodclassmethod#
def make_nothing() -> SelfConstruct a Nothing value.
make_somemethodclassmethod#
def make_some(value: T | None, /) -> SelfConvert an Optional[T] value to a Maybe value.
some = Maybe.make_some(42)assert some.is_just()
none = Maybe.make_some(None)assert none.is_nothing()mapmethod#
def map(func: Callable[[T], U], /) -> Maybe[U]Apply a function that maps the inner value to a new value, if any. Otherwise, return Nothing.
See Option::map.
j = just[int](42)assert j.map(lambda x: x + 1) == just(43)
n = nothing[int]()assert n.map(lambda x: x + 1) == nothing()map_ormethod#
def map_or(default: U, func: Callable[[T], U]) -> UMap the inner value using a function, or use the default value if absent.
j = just[int](42)n = nothing[int]()
assert j.map_or(0, lambda x: x + 1) == 43assert n.map_or(0, lambda x: x + 1) == 0map_or_elsemethod#
def map_or_else(default: Callable[[], U], func: Callable[[T], U]) -> UMap the inner value using a function, or use a lazy default value if absent.
j = just[int](42)n = nothing[int]()
assert j.map_or_else(lambda: 0, lambda x: x + 1) == 43assert n.map_or_else(lambda: 0, lambda x: x + 1) == 0ok_ormethod#
def ok_or(error: E, /) -> Result[T, E]Convert the Maybe to a Result, converting Just to Ok and Nothing to a provided Err value.
The Err value is eagerly evaluated.
j = just[int](42)n = nothing[int]()
assert j.ok_or("error").unwrap() == 42assert n.ok_or("error").unwrap_err() == "error"ok_or_elsemethod#
def ok_or_else(func: Callable[[], E], /) -> Result[T, E]Convert the Maybe to a Result, converting Just to Ok and Nothing to a lazily evaluated Err value.
j = just[int](42)n = nothing[int]()
assert j.ok_or_else(lambda: 0).unwrap() == 42assert n.ok_or_else(lambda: "error").unwrap_err() == "error"or_method#
def or_(other: Maybe[T], /) -> Maybe[T]If the value is Just, return a shallow copy of itself. Otherwise, return the other Maybe’s shallow copy.
Notice that the right-hand side should be a Maybe object with the same inner type,
although this is not enforced at runtime.
And also this method does not short-circuit.
j1 = just[int](42)j2 = just[int](114514)
assert (j1 | j2).unwrap() == 42assert (nothing[int]() | j2).unwrap() == 114514assert (j1 | nothing[int]()).unwrap() == 42or_elsemethod#
def or_else(func: Callable[[], Maybe[T]], /) -> Maybe[T]Return a shallow copy of the Maybe if it contains a value, otherwise call a function to get a result.
puremethodclassmethod#
def pure(value, /) -> MaybeImplementation of Applicative.pure, which is equivalent to Maybe.make_just.
replacemethod#
def replace(value: T, /) -> Maybe[T]Replace the inner value with a new value, returning the old value.
After replacement, self will always have a value.
j = just[int](42)old = j.replace(114514)assert j == just(114514)assert old == just(42)
n = nothing[int]()old = n.replace(1919810)assert n == just(1919810)assert old == nothing()takemethod#
def take() -> Maybe[T]Take the inner value out and leave no value in place.
j = just[int](42)out = j.take()assert j.is_nothing()assert out == just(42)take_ifmethod#
def take_if(pred: Callable[[T], bool], /) -> Maybe[T]Take the inner value out if it satisfies the predicate, and leave no value in place. Otherwise, take out nothing.
j = just[int](42)out = j.take_if(lambda x: x > 0)assert j.is_nothing()assert out == just(42)
j = just[int](42)out = j.take_if(lambda x: x < 0)assert j.is_just()assert out == nothing()tapmethod#
def tap(func: Callable[[T], Any], /) -> Maybe[T]Call a function with the inner value, if any, and return the Maybe itself.
Unlike Option::inspect, this method does not require the function to return None.
j = just[int](42)j.tap(print) # prints 42unwrapmethod#
def unwrap() -> TUnwrap the inner value, if any. Otherwise, raise a ValueError.
j = just[int](42)n = nothing[int]()
assert j.unwrap() == 42
try: n.unwrap()except ValueError: passelse: assert FalseRaises
ValueError- If the value is a
Nothing.
unwrap_ormethod#
def unwrap_or(default: T, /) -> TUnwrap the inner value, or return a default value if absent.
j = just[int](42)n = nothing[int]()assert j.unwrap_or(0) == 42assert n.unwrap_or(0) == 0unwrap_or_elsemethod#
def unwrap_or_else(func: Callable[[], T], /) -> TUnwrap the inner value, or return a value computed by a function if absent.
unwrap_uncheckedmethod#
def unwrap_unchecked() -> T | AnyReturn the inner value without checking if it is a Just or Nothing.
xormethod#
def xor(other: Maybe[T], /) -> Maybe[T]If only one side has a value, return that side. Otherwise, return a Nothing.
j1 = just[int](42)j2 = just[int](114514)assert j1.xor(j2).is_nothing()zipmethod#
def zip(*others: *tuple[()] | *tuple[Maybe[T1]] | *tuple[Maybe[T1], Maybe[T2]] | *tuple[Maybe[T1], Maybe[T2], Maybe[T3]] | *tuple[Maybe[T1], Maybe[T2], Maybe[T3], Maybe[T4]]) -> Maybe[tuple[T]] | Maybe[tuple[T, T1]] | Maybe[tuple[T, T1, T2]] | Maybe[tuple[T, T1, T2, T3]] | Maybe[tuple[T, T1, T2, T3, T4]]Combine multiple Maybe values into a single Maybe value containing a tuple of them.
If all values are Just, return a Just value with a tuple of inner values.
Otherwise, return a Nothing.
Calling this method with no arguments is equivalent to calling map with a tuple constructor.
j1 = just[int](42)j2 = just[int](114514)assert j1.zip(j2).unwrap() == (42, 114514)
n = nothing[int]()assert j1.zip(n).is_nothing()justclass#
class justConstructs a Just value.
This function is exposed in the package namespace.
j: Maybe[int] = just(42)j = just[int](42)
assert j.is_just()assert j.unwrap() == 42__match_args__attributeclass attributeinstance attribute#
__match_args__ = ('_val')__slots__attributeclass attributeinstance attribute#
__slots__ = ()nothingclass#
class nothingConstructs a Nothing value.
Notice that this is not a literal.
This function is exposed in the package namespace.
n: Maybe[int] = nothing()n = nothing[int]()
assert n.is_nothing()__match_args__attributeclass attributeinstance attribute#
__match_args__ = ()__slots__attributeclass attributeinstance attribute#
__slots__ = ()someclass#
class someConverts an Optional[T] to a Maybe[T] value.
This function is exposed in the package namespace.
something: Maybe[int] = some(42)assert something.is_just()assert something.unwrap() == 42
something = some[int](None)assert something.is_nothing()__slots__attributeclass attributeinstance attribute#
__slots__ = ()