.. _maybe: Maybe ===== The ``Maybe`` container is used when a series of computations could return ``None`` at any point. Maybe container --------------- ``Maybe`` consist of two types: ``Some`` and ``Nothing``. We have a convenient method to create different ``Maybe`` types based on just a single value: .. code:: python >>> from returns.maybe import Maybe >>> assert str(Maybe.from_optional(1)) == '' >>> assert str(Maybe.from_optional(None)) == '' We also have another method called ``.from_value`` that behaves a bit differently: .. code:: python >>> from returns.maybe import Maybe >>> assert str(Maybe.from_value(1)) == '' >>> assert str(Maybe.from_value(None)) == '' Usage ~~~~~ It might be very useful for complex operations like the following one: .. code:: python >>> 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 '' >>> assert get_street_address(empty_user) == Nothing >>> assert get_street_address(empty_address) == Nothing >>> assert get_street_address(empty_street) == Nothing Optional type ~~~~~~~~~~~~~ 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: .. code:: python 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. Pattern Matching ---------------- ``Maybe`` values can be matched using the new feature of Python 3.10, `Structural Pattern Matching `_, see the example below: .. literalinclude:: ../../tests/test_examples/test_maybe/test_maybe_pattern_matching.py Decorators ---------- Limitations ~~~~~~~~~~~ Typing will only work correctly if :ref:`our mypy plugin ` is used. This happens due to `mypy issue `_. maybe ~~~~~ 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: .. code:: python >>> 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) FAQ --- How can I turn Maybe into Optional again? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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: .. code:: python >>> 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. How to model absence of value vs presence of None value? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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: .. code:: python >>> assert Some(None) != Nothing There's a special way to work with a type like this: .. code:: python >>> 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: .. code:: python >>> 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? .. code:: python >>> 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 :meth:`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. Why there's no IOMaybe? ~~~~~~~~~~~~~~~~~~~~~~~ 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 :ref:`converters`. Why Maybe does not have alt method? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Well, because ``Maybe`` only has a single failed value: ``Nothing`` and it cannot be altered. But, ``Maybe`` has :meth:`returns.maybe.Maybe.or_else_call` method to call a passed callback function with zero argument on failed container: .. code:: python >>> 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. Further reading --------------- - `Option Monads in Rust `_ - `Option overview in TypeScript `_ - `Maybe not - Rich Hickey `_ API Reference ------------- .. autoclasstree:: returns.maybe :strict: .. automodule:: returns.maybe :members: