Skip to content

apfel.container.maybe

A container that optionally holds a value.

See Option and Maybe.

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 1
if optional is None:
...
else:
do_something(optional)
# Usage 2
do_something(optional) if optional is not None else ...
# Usage 3
optional 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#

Classes

NameDescription
MaybeA container that optionally holds a value.
justConstructs a Just value.
nothingConstructs a Nothing value.
someConverts an Optional[T] to a Maybe[T] value.

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__() -> bool

Equivalent to is_just.

__class_getitem__method#

def __class_getitem__(item) -> Maybe[U]

__eq__method#

def __eq__(other: Any, /) -> bool

If 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__() -> int

Return 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__() -> str

and_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() == True
assert (j1 & j2).unwrap() == True
assert (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() == 1919810

applymethod#

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, /) -> T

Unwrap 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() == 42
assert 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, /) -> T

Get 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) == 114514

get_or_insert_withmethod#

def get_or_insert_with(func: Callable[[], T], /) -> T

Get 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) == 114514

insertmethod#

def insert(value: T, /) -> T

Insert a value and returns it.

is_justmethod#

def is_just() -> bool

Check if the value is a Just.

is_just_andmethod#

def is_just_and(pred: Callable[[T], bool], /) -> bool

Check if the value is a Just and satisfies the predicate.

is_nothingmethod#

def is_nothing() -> bool

Check if the value is a Nothing.

make_justmethodclassmethod#

def make_just(value: T, /) -> Self

Construct a Just value.

make_nothingmethodclassmethod#

def make_nothing() -> Self

Construct a Nothing value.

make_somemethodclassmethod#

def make_some(value: T | None, /) -> Self

Convert 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]) -> U

Map 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) == 43
assert n.map_or(0, lambda x: x + 1) == 0

map_or_elsemethod#

def map_or_else(default: Callable[[], U], func: Callable[[T], U]) -> U

Map 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) == 43
assert n.map_or_else(lambda: 0, lambda x: x + 1) == 0

ok_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() == 42
assert 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() == 42
assert 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() == 42
assert (nothing[int]() | j2).unwrap() == 114514
assert (j1 | nothing[int]()).unwrap() == 42

or_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, /) -> Maybe

Implementation 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 42

unwrapmethod#

def unwrap() -> T

Unwrap 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: pass
else: assert False

Raises

ValueError
If the value is a Nothing.

unwrap_ormethod#

def unwrap_or(default: T, /) -> T

Unwrap the inner value, or return a default value if absent.

j = just[int](42)
n = nothing[int]()
assert j.unwrap_or(0) == 42
assert n.unwrap_or(0) == 0

unwrap_or_elsemethod#

def unwrap_or_else(func: Callable[[], T], /) -> T

Unwrap the inner value, or return a value computed by a function if absent.

unwrap_uncheckedmethod#

def unwrap_unchecked() -> T | Any

Return 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 just

Constructs 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__ = ()

__class_getitem__method#

def __class_getitem__(item) -> Maybe[U]

__instancecheck__methodclassmethod#

def __instancecheck__(instance)

__new__method#

def __new__(value: T, /) -> Maybe[T]

nothingclass#

class nothing

Constructs 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__ = ()

__class_getitem__method#

def __class_getitem__(item) -> Maybe[U]

__instancecheck__methodclassmethod#

def __instancecheck__(instance)

__new__method#

def __new__() -> Maybe[T]

someclass#

class some

Converts 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__ = ()

__class_getitem__method#

def __class_getitem__(item) -> Maybe[U]

__new__method#

def __new__(value: T, /) -> Maybe[T]