The Maybe
container is used when a series of computations
could return None
at any point.
Maybe
consist of two types: Some
and Nothing
.
We have a convenient method to create different Maybe
types
based on just a single value:
>>> from returns.maybe import Maybe
>>> assert str(Maybe.from_optional(1)) == '<Some: 1>'
>>> assert str(Maybe.from_optional(None)) == '<Nothing>'
We also have another method called .from_value
that behaves a bit differently:
>>> from returns.maybe import Maybe
>>> assert str(Maybe.from_value(1)) == '<Some: 1>'
>>> assert str(Maybe.from_value(None)) == '<Some: None>'
It might be very useful for complex operations like the following one:
>>> from attr import dataclass
>>> from typing import Optional
>>> from returns.maybe import Maybe, Nothing
>>> @dataclass
... class Address(object):
... street: Optional[str]
>>> @dataclass
... class User(object):
... address: Optional[Address]
>>> @dataclass
... class Order(object):
... user: Optional[User]
>>> def get_street_address(order: Order) -> Maybe[str]:
... return Maybe.from_optional(order.user).bind_optional(
... lambda user: user.address,
... ).bind_optional(
... lambda address: address.street,
... )
>>> with_address = Order(User(Address('Some street')))
>>> empty_user = Order(None)
>>> empty_address = Order(User(None))
>>> empty_street = Order(User(Address(None)))
>>> str(get_street_address(with_address)) # all fields are not None
'<Some: Some street>'
>>> assert get_street_address(empty_user) == Nothing
>>> assert get_street_address(empty_address) == Nothing
>>> assert get_street_address(empty_street) == Nothing
One may ask: “How is that different to the Optional[]
type?”
That’s a really good question!
Consider the same code to get the street name
without Maybe
and using raw Optional
values:
order: Order # some existing Order instance
street: Optional[str] = None
if order.user is not None:
if order.user.address is not None:
street = order.user.address.street
It looks way uglier and can grow even more uglier and complex when new logic will be introduced.
Maybe
values can be matched using the new feature of Python 3.10,
Structural Pattern Matching,
see the example below:
from dataclasses import dataclass
from typing import Final
from returns.maybe import Maybe, Nothing, Some
@dataclass
class _Book(object):
book_id: int
name: str
_BOOK_LIST: Final = (
_Book(book_id=1, name='Category Theory for Programmers'),
_Book(book_id=2, name='Fluent Python'),
_Book(book_id=3, name='Learn You Some Erlang for Great Good'),
_Book(book_id=4, name='Learn You a Haskell for Great Good'),
)
def _find_book(book_id: int) -> Maybe[_Book]:
for book in _BOOK_LIST:
if book.book_id == book_id:
return Some(book)
return Nothing
if __name__ == '__main__':
desired_book = _find_book(2)
match desired_book:
# Matches any `Some` instance that contains a book named `Fluent Python`
case Some(_Book(name='Fluent Python')):
print('"Fluent Python" was found')
# Matches any `Some` instance and binds its value to the `book` variable
case Some(book):
print('Book found: {0}'.format(book.name))
# Matches `Nothing` instance
case Maybe.empty:
print('Not found the desired book!')
Typing will only work correctly if our mypy plugin is used. This happens due to mypy issue.
Sometimes we have to deal with functions
that dears to return Optional
values!
We have to work with it the carefully
and write if x is not None:
everywhere.
Luckily, we have your back! maybe
function decorates
any other function that returns Optional
and converts it to return Maybe
instead:
>>> from typing import Optional
>>> from returns.maybe import Maybe, Some, maybe
>>> @maybe
... def number(num: int) -> Optional[int]:
... if num > 0:
... return num
... return None
>>> result: Maybe[int] = number(1)
>>> assert result == Some(1)
When working with regular Python,
you might need regular Optional[a]
values.
You can easily get one from your Maybe
container at any point in time:
>>> from returns.maybe import Maybe
>>> assert Maybe.from_optional(1).value_or(None) == 1
>>> assert Maybe.from_optional(None).value_or(None) == None
As you can see, revealed type of .value_or(None)
is Optional[a]
.
Use it a fallback.
Let’s say you have this dict
: values = {'a': 1, 'b': None}
So, you can have two types of None
here:
values.get('b')
values.get('c')
But, they are different!
The first has explicit None
value,
the second one has no given key and None
is used as a default.
You might need to know exactly which case you are dealing with.
For example, in validation.
So, the first thing to remember is that:
>>> assert Some(None) != Nothing
There’s a special way to work with a type like this:
>>> values = {'a': 1, 'b': None}
>>> assert Maybe.from_value(values).map(lambda d: d.get('a')) == Some(1)
>>> assert Maybe.from_value(values).map(lambda d: d.get('b')) == Some(None)
In contrast, you can ignore both None
values easily:
>>> assert Maybe.from_value(values).bind_optional(
... lambda d: d.get('a'),
... ) == Some(1)
>>> assert Maybe.from_value(values).bind_optional(
... lambda d: d.get('b'),
... ) == Nothing
So, how to write a complete check for a value: both present and missing?
>>> from typing import Optional, Dict, TypeVar
>>> from returns.maybe import Maybe, Some, Nothing
>>> _Key = TypeVar('_Key')
>>> _Value = TypeVar('_Value')
>>> def check_key(
... heystack: Dict[_Key, _Value],
... needle: _Key,
... ) -> Maybe[_Value]:
... if needle not in heystack:
... return Nothing
... return Maybe.from_value(heystack[needle]) # try with `.from_optional`
>>> real_values = {'a': 1}
>>> opt_values = {'a': 1, 'b': None}
>>> assert check_key(real_values, 'a') == Some(1)
>>> assert check_key(real_values, 'b') == Nothing
>>> # Type revealed: returns.maybe.Maybe[builtins.int]
>>> assert check_key(opt_values, 'a') == Some(1)
>>> assert check_key(opt_values, 'b') == Some(None)
>>> assert check_key(opt_values, 'c') == Nothing
>>> # Type revealed: returns.maybe.Maybe[Union[builtins.int, None]]
Choose wisely between .from_value
and .map
,
and .from_optional
and .bind_optional
.
They are similar, but do different things.
Note that you can also use returns.pipeline.is_successful()
to check if the value is present.
See the original issue about Some(None) for more details and the full history.
We do have IOResult
, but we don’t have IOMaybe
. Why?
Because when dealing with IO
there are a lot of possible errors.
And Maybe
represents just None
and the value.
It is not useful for IO
related tasks.
So, use Result
instead, which can represent what happened to your IO
.
You can convert Maybe
to Result
and back again with special Converters.
Well, because Maybe
only has a single failed value:
Nothing
and it cannot be altered.
But, Maybe
has returns.maybe.Maybe.or_else_call()
method to call
a passed callback function with zero argument on failed container:
>>> from returns.maybe import Some, Nothing
>>> assert Some(1).or_else_call(lambda: 2) == 1
>>> assert Nothing.or_else_call(lambda: 2) == 2
This method is unique to Maybe
container.
Bases: BaseContainer
, SupportsKindN
[Maybe
, _ValueType
, NoReturn
, NoReturn
], MaybeBasedN
[_ValueType
, None
, NoReturn
]
Represents a result of a series of computations that can return None
.
An alternative to using exceptions or constant is None
checks.
Maybe
is an abstract type and should not be instantiated directly.
Instead use Some
and Nothing
.
ClassVar
[Maybe[Any]] = <Nothing>¶Alias for Nothing
Typesafe equality comparison with other Result objects.
Composes successful container with a pure function.
>>> from returns.maybe import Some, Nothing
>>> def mappable(string: str) -> str:
... return string + 'b'
>>> assert Some('a').map(mappable) == Some('ab')
>>> assert Nothing.map(mappable) == Nothing
function (Callable
[[TypeVar
(_ValueType
, covariant=True)], TypeVar
(_NewValueType
)]) –
Maybe
[TypeVar
(_NewValueType
)]
Calls a wrapped function in a container on this container.
>>> from returns.maybe import Some, Nothing
>>> def appliable(string: str) -> str:
... return string + 'b'
>>> assert Some('a').apply(Some(appliable)) == Some('ab')
>>> assert Some('a').apply(Nothing) == Nothing
>>> assert Nothing.apply(Some(appliable)) == Nothing
>>> assert Nothing.apply(Nothing) == Nothing
Composes successful container with a function that returns a container.
>>> from returns.maybe import Nothing, Maybe, Some
>>> def bindable(string: str) -> Maybe[str]:
... return Some(string + 'b')
>>> assert Some('a').bind(bindable) == Some('ab')
>>> assert Nothing.bind(bindable) == Nothing
Binds a function returning an optional value over a container.
>>> from returns.maybe import Some, Nothing
>>> from typing import Optional
>>> def bindable(arg: str) -> Optional[int]:
... return len(arg) if arg else None
>>> assert Some('a').bind_optional(bindable) == Some(1)
>>> assert Some('').bind_optional(bindable) == Nothing
function (Callable
[[TypeVar
(_ValueType
, covariant=True)], Optional
[TypeVar
(_NewValueType
)]]) –
Maybe
[TypeVar
(_NewValueType
)]
Composes failed container with a function that returns a container.
>>> from returns.maybe import Maybe, Some, Nothing
>>> def lashable(arg=None) -> Maybe[str]:
... return Some('b')
>>> assert Some('a').lash(lashable) == Some('a')
>>> assert Nothing.lash(lashable) == Some('b')
We need this feature to make Maybe
compatible
with different Result
like operations.
Allows working with unwrapped values of containers in a safe way.
>>> from returns.maybe import Maybe, Some, Nothing
>>> assert Maybe.do(
... first + second
... for first in Some(2)
... for second in Some(3)
... ) == Some(5)
>>> assert Maybe.do(
... first + second
... for first in Some(2)
... for second in Nothing
... ) == Nothing
See Do Notation to learn more.
expr (Generator
[TypeVar
(_NewValueType
), None
, None
]) –
Maybe
[TypeVar
(_NewValueType
)]
Get value from successful container or default value from failed one.
>>> from returns.maybe import Nothing, Some
>>> assert Some(0).value_or(1) == 0
>>> assert Nothing.value_or(1) == 1
default_value (TypeVar
(_NewValueType
)) –
Union
[TypeVar
(_ValueType
, covariant=True), TypeVar
(_NewValueType
)]
Get value from successful container or default value from failed one.
Really close to value_or()
but works with lazy values.
This method is unique to Maybe
container, because other containers
do have .alt
method.
But, Maybe
does not have this method.
There’s nothing to alt
in Nothing
.
Instead, it has this method to execute some function if called on a failed container:
>>> from returns.maybe import Some, Nothing
>>> assert Some(1).or_else_call(lambda: 2) == 1
>>> assert Nothing.or_else_call(lambda: 2) == 2
It might be useful to work with exceptions as well:
>>> def fallback() -> NoReturn:
... raise ValueError('Nothing!')
>>> Nothing.or_else_call(fallback)
Traceback (most recent call last):
...
ValueError: Nothing!
function (Callable
[[], TypeVar
(_NewValueType
)]) –
Union
[TypeVar
(_ValueType
, covariant=True), TypeVar
(_NewValueType
)]
Get value from successful container or raise exception for failed one.
>>> from returns.maybe import Nothing, Some
>>> assert Some(1).unwrap() == 1
>>> Nothing.unwrap()
Traceback (most recent call last):
...
returns.primitives.exceptions.UnwrapFailedError
TypeVar
(_ValueType
, covariant=True)
Get failed value from failed container or raise exception from success.
>>> from returns.maybe import Nothing, Some
>>> assert Nothing.failure() is None
>>> Some(1).failure()
Traceback (most recent call last):
...
returns.primitives.exceptions.UnwrapFailedError
None
Creates new instance of Maybe
container based on a value.
>>> from returns.maybe import Maybe, Some
>>> assert Maybe.from_value(1) == Some(1)
>>> assert Maybe.from_value(None) == Some(None)
inner_value (TypeVar
(_NewValueType
)) –
Maybe
[TypeVar
(_NewValueType
)]
Creates new instance of Maybe
container based on an optional value.
>>> from returns.maybe import Maybe, Some, Nothing
>>> assert Maybe.from_optional(1) == Some(1)
>>> assert Maybe.from_optional(None) == Nothing
inner_value (Optional
[TypeVar
(_NewValueType
)]) –
Maybe
[TypeVar
(_NewValueType
)]
Bases: Maybe
[_ValueType
]
Represents a calculation which has succeeded and contains the value.
Quite similar to Success
type.
inner_value (TypeVar
(_ValueType
, covariant=True)) –
Decorator to convert None
-returning function to Maybe
container.
This decorator works with sync functions only. Example:
>>> from typing import Optional
>>> from returns.maybe import Nothing, Some, maybe
>>> @maybe
... def might_be_none(arg: int) -> Optional[int]:
... if arg == 0:
... return None
... return 1 / arg
>>> assert might_be_none(0) == Nothing
>>> assert might_be_none(1) == Some(1.0)
function (Callable
[[ParamSpec
(_FuncParams
)], Optional
[TypeVar
(_ValueType
, covariant=True)]]) –
Callable
[[ParamSpec
(_FuncParams
)], Maybe
[TypeVar
(_ValueType
, covariant=True)]]