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 and RequiresContextIOResult.

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.

All containers support special .from_value method to construct a new container from a raw value.

>>> from returns.result import Result

>>> str(Result.from_value(1))
'<Success: 1>'

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

bind() is used to literally bind two different containers together.

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)

And we have map() to use containers with regular functions.

>>> 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)
>>> str(result)
'<Success: 2>'

>>> result: Result[int, Any] = result.map(lambda state: state + 1)
>>> 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']>"

Note:

All containers support these methods.

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

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.

Composition

You can and should compose different containers together. Here’s a table of some compositions that do not make sense:

Needs transformation

You can use Converters to convert Maybe and Result containers. You can also use flatten to merge nested containers.

  • IO[Result[A, B]] 🤔, use returns.io.IOResult.from_typecast() and IOResult

  • IO[Maybe[A]] 🤔, use maybe_to_result and then returns.io.IOResult.from_typecast() to convert it to IOResult

  • IO[IO[A]] 🤔, use flatten

  • Maybe[Maybe[A]] 🤔, use flatten

  • Result[Result[A, B], C] 🤔, use flatten

  • Result[Maybe[A], B] 🤔, use maybe_to_result and then flatten

  • Maybe[Result[A, B]] 🤔, use result_to_maybe and then flatten

  • RequiresContext[env, Result[A, B]] 🤔, use RequiresContextResult.from_typecast and RequiresResultContext

  • RequiresContext[env, RequiresContext[env, A]] 🤔, use flatten

  • RequiresContextResult[env, RequiresContextResult[env, A, B], B] 🤔, use flatten

  • RequiresContext[env, IOResult[A, B]] 🤔, use RequiresContextIOResult.from_typecast and RequiresResultContext

  • RequiresContextIOResult[env, RequiresContextIOResult[env, A, B], B] 🤔, use flatten

Nope

  • Result[IO[A], B] 🚫

  • Result[A, IO[A]] 🚫

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.

__init__(inner_value)[source]

Wraps the given value in the Container.

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

Return type

None

__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

Here are our interfaces (or protocols to be more specific) that we use inside our app:

graph TD; Rescueable Unwrapable Altable Applicative Unifiable Unitable Bindable Fixable Mappable Protocol --> Bindable Protocol --> Unifiable Protocol --> Mappable Protocol --> Fixable Protocol --> Rescueable Protocol --> Unwrapable Protocol --> Altable Protocol --> Applicative Applicative --> Unitable Protocol --> Unitable
class Bindable(*args, **kwargs)[source]

Bases: typing_extensions.Protocol

Represents a “context” in which calculations can be executed.

Bindable allows you to bind together a series of calculations while maintaining the context of that specific container.

bind(function)[source]

Applies ‘function’ to the result of a previous calculation.

And returns a new container. Works for containers that represent success. Is the opposite of rescue().

Parameters

function (Callable[[+_ValueType], Bindable[~_NewValueType]]) –

Return type

Bindable[~_NewValueType]

class Unifiable(*args, **kwargs)[source]

Bases: typing_extensions.Protocol

Represents a “context” in which calculations can be executed.

Unifiable allows you to bind together a series of calculations while maintaining the context of that specific container.

As the name suggests is used to unify error types while binding the value type.

unify(function)[source]

Applies ‘function’ to the result of a previous calculation.

And returns a new container. Works for containers that represent success. Works the same way as bind(), but has different type semantics. Is the opposite of rescue().

Parameters

function (Callable[[+_ValueType], Unifiable[~_NewValueType, ~_NewErrorType]]) –

Return type

Unifiable[~_NewValueType, Union[+_ErrorType, ~_NewErrorType]]

class Mappable(*args, **kwargs)[source]

Bases: typing_extensions.Protocol

Allows to chain wrapped values with regular functions.

Behaves like a functor.

map(function)[source]

Applies ‘function’ to the contents of the functor.

And returns a new functor value. Is the opposite of fix().

Has returns.pointfree.map() helper with the inverse semantic.

Parameters

function (Callable[[+_ValueType], ~_NewValueType]) –

Return type

Mappable[~_NewValueType]

class Fixable(*args, **kwargs)[source]

Bases: typing_extensions.Protocol

Represents containers that can be fixed and rescued.

fix(function)[source]

Applies ‘function’ to the error and transforms failure to success.

And returns a new functor value. Works for containers that represent failure. Is the opposite of map().

Parameters

function (Callable[[+_ErrorType], ~_NewValueType]) –

Return type

Fixable[~_NewValueType, +_ErrorType]

class Rescueable(*args, **kwargs)[source]

Bases: typing_extensions.Protocol

Represents a “context” in which calculations can be executed.

Rescueable allows you to bind together a series of calculations while maintaining the context of that specific container.

rescue(function)[source]

Applies ‘function’ to the result of a previous calculation.

And returns a new container. Works for containers that represent failure. Is the opposite of bind().

Parameters

function (Callable[[+_ErrorType], Rescueable[~_NewValueType, ~_NewErrorType]]) –

Return type

Rescueable[~_NewValueType, ~_NewErrorType]

class Unwrapable(*args, **kwargs)[source]

Bases: typing_extensions.Protocol

Represents containers that can unwrap and return its wrapped value.

value_or(default_value)[source]

Forces to unwrap value from container or return a default.

Parameters

default_value (~_NewValueType) –

Return type

Union[+_ValueType, ~_NewValueType]

unwrap()[source]

Custom magic method to unwrap inner value from container.

Should be redefined for ones that actually have values. And for ones that raise an exception for no values.

This method is the opposite of failure().

Return type

+_ValueType

failure()[source]

Custom magic method to unwrap inner value from the failed container.

This method is the opposite of unwrap().

Return type

+_ErrorType

class Altable(*args, **kwargs)[source]

Bases: typing_extensions.Protocol

Allows to unwrap failures.

alt(function)[source]

Uses ‘function’ to transform one error to another.

And returns a new functor value. Works for containers that represent failure. Is the opposite of map().

Parameters

function (Callable[[+_ErrorType], ~_NewErrorType]) –

Return type

Fixable[+_ValueType, ~_NewErrorType]

class Applicative(*args, **kwargs)[source]

Bases: typing_extensions.Protocol

Allows to create unit containers from raw values.

All containers should support this interface.

classmethod from_value(inner_value)[source]

This method is required to create new containers.

Parameters

inner_value (~_NewValueType) –

Return type

Unitable[~_NewValueType, Any]

apply(container)[source]

Calls a wrapped function in a container on this container.

Parameters

container (Applicative[Callable[[+_ValueType], ~_NewValueType]]) –

Return type

Applicative[~_NewValueType]

class Unitable(*args, **kwargs)[source]

Bases: returns.primitives.interfaces.Applicative, typing_extensions.Protocol

Allows to create unit values from success and failure.

This is a heavily Result-related class.

classmethod from_failure(inner_value)[source]

This method is required to create values that represent failure.

Parameters

inner_value (~_NewErrorType) –

Return type

Unitable[Any, ~_NewErrorType]