Container: the concept

Container is a concept that allows you to write code without traditional error handling while maintaining the execution context.

We will show you its 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.

Railway oriented programming

We use a concept of Railway oriented programming. It mean that our code can go on two tracks:

  1. Successful one: where everything goes perfectly: HTTP requests work, database is always serving us data, parsing values does not failed

  2. Failed one: where something went wrong

We can switch from track to track: we can fail something or we can rescue the situation.

graph LR S1 --> S3 S3 --> S5 S5 --> S7 F2 --> F4 F4 --> F6 F6 --> F8 S1 -- Fail --> F2 F2 -- Fix --> S3 S3 -- Fail --> F4 S5 -- Fail --> F6 F6 -- Rescue --> S7 style S1 fill:green style S3 fill:green style S5 fill:green style S7 fill:green style F2 fill:red style F4 fill:red style F6 fill:red style F8 fill:red

Railway oriented programming.

Working with containers

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

The difference is simple:

  • map works with functions that return regular values

  • bind works with functions that return other containers

Container.bind is used to literally bind two different containers together.

from returns.result import Result, Success

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

result = Success(1).bind(make_http_call)
# => Will be equal to either Success[int] or Failure[str]

So, the rule is: whenever you have some impure functions, it should return a container type instead.

And we use Container.map to use containers with pure functions.

from returns.result import Success

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

result = Success(1).map(double)
# => Will be equal to Success(2)

Returning execution to the right track

We also support two special methods to work with “failed” types like Failure and Nothing:

  • Container.fix is the opposite of map method that works only when container is in failed state

  • Container.rescue is the opposite of bind method that works only when container is in failed state

fix can be used to fix some fixable errors during the pipeline execution:

from returns.result import Failure

def double(state: int) -> float:
    return state * 2.0

Failure(1).fix(double)
# => Will be equal to Success(2.0)

rescue can return any container type you want. It can also fix your flow and get on the successful track again:

from returns.result import Result, Failure, Success

def fix(state: Exception) -> Result[int, Exception]:
    if isinstance(state, ZeroDivisionError):
        return Success(0)
    return Failure(state)

Failure(ZeroDivisionError).rescue(fix)
# => Will be equal to Success(0)

Unwrapping values

And we have two more functions to unwrap inner state of containers into a regular types:

  • Container.value_or returns a value if it is possible, returns default_value otherwise

  • Container.unwrap returns a value if it is possible, raises UnwrapFailedError otherwise

from returns.result import Failure, Success

Success(1).value_or(None)
# => 1

Success(0).unwrap()
# => 0

Failure(1).value_or(default_value=100)
# => 100

Failure(1).unwrap()
# => Traceback (most recent call last): UnwrapFailedError

The most user-friendly way to use unwrap method is with pipeline.

For failing containers you can use Container.failure to unwrap the failed state:

Failure(1).failure()
# => 1

Success(1).failure()
# => Traceback (most recent call last): UnwrapFailedError

Be careful, since this method will raise an exception when you try to failure a successful container.

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.

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.

However, this is still good old python type system, and it has its drawbacks.

API Reference

graph TD; _BaseContainer GenericContainerOneSlot GenericContainerTwoSlots Container _BaseContainer --> Container Generic --> GenericContainerOneSlot Container --> GenericContainerOneSlot Generic --> GenericContainerTwoSlots Container --> GenericContainerTwoSlots
class Container(inner_value)[source]

Bases: returns.primitives.container._BaseContainer

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

You won’t create ‘Container’ instances directly. Instead, sub-classes implement specific contexts. Monads allow you to bind together a series of calculations while maintaining the context of that specific monad.

This is an abstract class with the API declaration.

_inner_value

Wrapped internal immutable state.

map(function)[source]

Applies ‘function’ to the contents of the functor.

And returns a new functor value. Works for monads that represent success. Is the opposite of fix().

bind(function)[source]

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

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

fix(function)[source]

Applies ‘function’ to the contents of the functor.

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

rescue(function)[source]

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

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

value_or(default_value)[source]

Forces to unwrap value from monad or return a default.

unwrap()[source]

Custom magic method to unwrap inner value from monad.

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

failure()[source]

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

This method is the opposite of unwrap().

class GenericContainerOneSlot(inner_value)[source]

Bases: typing.Generic, returns.primitives.container.Container

Base class for monads with one typed slot.

Use this type for generic inheritance only. Use Container as a general type for polymorphism.

class GenericContainerTwoSlots(inner_value)[source]

Bases: typing.Generic, returns.primitives.container.Container

Base class for monads with two typed slot.

Use this type for generic inheritance only. Use Container as a general type for polymorphism.

Features Result