Container: the concept

Container is a concept that allows you to write code around the existing wrapped values while maintaining the execution context.

List of supported containers:

  • Maybe to handle None cases

  • Result to handle possible exceptions

  • IO to mark explicit IO actions

  • Future to work with async code

  • RequiresContext to pass context to your functions (DI and similar)

There are also some combintations like IOResult, FutureResult, RequiresContextResult, RequiresContextIOResult and RequiresContextFutureResult.

We will show you container’s simple API of one attribute and several simple methods.

Basics

The main idea behind a container is that it wraps some internal state. That’s what ._inner_value is used for.

And we have several functions to create new containers based on the previous state. And we can see how this state is evolving during the execution.

graph LR F1["Container(Initial)"] --> F2["Container(UserId(1))"] F2 --> F3["Container(UserAccount(156))"] F3 --> F4["Container(FailedLoginAttempt(1))"] F4 --> F5["Container(SentNotificationId(992))"]

State evolution.

Working with a container

We use two methods to create a new container from the previous one. bind and map.

The difference is simple:

  • map works with functions that return regular value

  • bind works with functions that return new container of the same type

We have returns.interfaces.mappable.MappableN.map() to compose containers with regular functions.

Here’s how it looks:

graph LR F1["Container[A]"] -- "map(function)" --> F2["Container[B]"] style F1 fill:green style F2 fill:green

Illustration of map method.

>>> from typing import Any
>>> from returns.result import Success, Result

>>> def double(state: int) -> int:
...     return state * 2

>>> result: Result[int, Any] = Success(1).map(double)
>>> assert str(result) == '<Success: 2>'

>>> result: Result[int, Any] = result.map(lambda state: state + 1)
>>> assert str(result) == '<Success: 3>'

The same works with built-in functions as well:

>>> from returns.io import IO

>>> io = IO('bytes').map(list)
>>> str(io)
"<IO: ['b', 'y', 't', 'e', 's']>"

The second method is bind. It is a bit different. We pass a function that returns another container to it. returns.interfaces.bindable.BindableN.bind() is used to literally bind two different containers together.

Here’s how it looks:

graph LR F1["Container[A]"] -- "bind(function)" --> F2["Container[B]"] F1["Container[A]"] -- "bind(function)" --> F3["Container[C]"] style F1 fill:green style F2 fill:green style F3 fill:red

Illustration of bind method.

from returns.result import Result, Success

def may_fail(user_id: int) -> Result[float, str]:
    ...

value: Result[int, str] = Success(1)
# Can be assumed as either Success[float] or Failure[str]:
result: Result[float, str] = value.bind(may_fail)

Note

All containers support these methods. Because all containers implement returns.interfaces.mappable.MappableN and returns.interfaces.bindable.BindableN.

You can read more about methods that some other containers support and interfaces behind them.

Instantiating a container

All returns.interfaces.applicative.ApplicativeN containers support special .from_value method to construct a new container from a raw value.

>>> from returns.result import Result
>>> assert str(Result.from_value(1)) == '<Success: 1>'

There are also other methods in other interfaces. For example, here are some of them:

>>> from returns.maybe import Maybe, Some, Nothing
>>> assert Maybe.from_optional(1) == Some(1)
>>> assert Maybe.from_optional(None) == Nothing
>>> from returns.result import Result, Failure
>>> assert Result.from_failure(1) == Failure(1)

There are many other constuctors! Check out concrete types and their interfaces.

Working with multiple containers

Iterable of containers

You might end up with an iterable of containers:

>>> from returns.maybe import Maybe, Some, Nothing, maybe

>>> source = {'a': 1, 'b': 2}

>>> fetched_values: Maybe[int] = [
...     maybe(source.get)(key)
...     for key in ('a', 'b')
... ]

To work with iterable of containers, it is recommended to cast it into a container with the iterable inside:

>>> assert Maybe.from_iterable(fetched_values) == Some((1, 2))

Any falsy values will result in a falsy result (pun intended):

>>> fetched_values: Maybe[int] = [
...     maybe(source.get)(key)
...     for key in ('a', 'c')  # 'c' is missing!
... ]
>>> assert Maybe.from_iterable(fetched_values) == Nothing

We support any Iterable[T] input type and return a Container[Sequence[T]]. All containers support this method.

Multiple container arguments

We have already seen how we can work with one container and functions that receive a single argument.

Let’s say you have a function of two arguments and two containers:

def sum_two_numbers(first: int, second: int) -> int:
    return first + second

And here are our two containers:

from returns.io import IO

one = IO(1)
two = IO(2)

Naive approach to compose two IO containers and a function would be two hard to show here. Luckly, we support partial application and .apply() method.

Here are the required steps:

  1. We make sum_two_numbers to receive partial arguments

  2. We create a new container that wraps sum_two_numbers function as a value

  3. We then call .apply() twice to pass each value

It can be done like so:

>>> from returns.curry import curry
>>> from returns.io import IO

>>> @curry
... def sum_two_numbers(first: int, second: int) -> int:
...     return first + second

>>> one = IO(1)
>>> two = IO(2)
>>> assert two.apply(one.apply(IO(sum_two_numbers))) == IO(3)

But, there are other ways to make sum_two_numbers partial. One can use:

>>> one = IO(1)
>>> two = IO(2)
>>> assert two.apply(one.apply(
...     IO(lambda x: lambda y: sum_two_numbers(x, y)),
... )) == IO(3)

It would be faster, but not as elegant (and type-safe).

Immutability

We like to think of returns as immutable structures. You cannot mutate the inner state of the created container, because we redefine __setattr__ and __delattr__ magic methods.

You cannot also set new attributes to container instances, since we are using __slots__ for better performance and strictness.

Well, nothing is really immutable in python, but you were warned.

We also provide returns.primitives.types.Immutable mixin that users can use to quickly make their classes immutable.

Type safety

We try to make our containers optionally type safe.

What does it mean?

  1. It is still good old python, do whatever you want without mypy

  2. If you are using mypy you will be notified about type violations

We also ship PEP561 compatible .pyi files together with the source code. In this case these types will be available to users when they install our application.

We also ship custom mypy plugins to overcome some existing problems, please make sure to use them, since they increase your developer experience and type-safety level:

Check out our docs on using our mypy plugins.

API Reference

BaseContainer is a base class for all other containers. It defines some basic things like representation, hashing, pickling, etc.

graph TD; BaseContainer Immutable --> BaseContainer
class BaseContainer(inner_value)[source]

Bases: returns.primitives.types.Immutable

Utility class to provide all needed magic methods to the context.

__slots__ = ('_inner_value',)
__init__(inner_value)[source]

Wraps the given value in the Container.

‘value’ is any arbitrary value of any type including functions.

__str__()[source]

Converts to string.

Return type

str

__eq__(other)[source]

Used to compare two ‘Container’ objects.

Parameters

other (Any) –

Return type

bool

__hash__()[source]

Used to use this value as a key.

Return type

int

__getstate__()[source]

That’s how this object will be pickled.

Return type

Any

__setstate__(state)[source]

Loading state from pickled data.

Parameters

state (Any) –

Return type

None

__abstractmethods__ = frozenset({})
__annotations__ = {'_inner_value': typing.Any}
__module__ = 'returns.primitives.container'
container_equality(self, other)[source]

Function to compare similar containers.

Compares both their types and their inner values.

Parameters
  • self (KindN[~_EqualType, Any, Any, Any]) –

  • other (KindN[~_EqualType, Any, Any, Any]) –

Return type

bool