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

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

There are also some combintations like IOResult, 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.

Working with containers

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.

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:

[mypy]
plugins =
  returns.contrib.mypy.decorator_plugin

You can have a look at the suggested mypy configuration in our own repository.

Composition

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

Needs transformation

  • 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]] 🚫

  • Result[A, Maybe[B]] 🚫

  • Result[A, Result[B, C]] 🚫

  • Maybe[IO[A]] 🚫

  • RequiresContext[IO[A], B] 🚫

  • IO[RequiresContext[A, B] 🚫

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

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.

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.

Return type

None

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

graph TD; Unwrapable Rescueable Mappable Fixable Bindable Altable Instanceable Unitable Protocol --> Bindable Protocol --> Mappable Protocol --> Fixable Protocol --> Rescueable Protocol --> Unwrapable Protocol --> Altable Protocol --> Instanceable 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().

Return type

Bindable[~_NewValueType]

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

Bases: typing_extensions.Protocol

Allows to chain wrapped values with regular functions.

Behaves like functor.

map(function)[source]

Applies ‘function’ to the contents of the functor.

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

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().

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().

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.

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().

Return type

Fixable[+_ValueType, ~_NewErrorType]

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

Bases: typing_extensions.Protocol

Allows to create unit containers from raw values.

This is heavily related to classes that do not have conunter-parts. Like IO and RequiresContext.

classmethod from_value(inner_value)[source]

This method is required to create new containers.

Return type

Unitable[~_NewValueType, Any]

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

Bases: typing_extensions.Protocol

Allows to create unit values from success and failure.

This is a heavily Result-related class.

classmethod from_success(inner_value)[source]

This method is required to create values that represent success.

Return type

Unitable[~_NewValueType, Any]

classmethod from_failure(inner_value)[source]

This method is required to create values that represent failure.

Return type

Unitable[Any, ~_NewErrorType]