Railway oriented programming

Containers can serve many different purposes (while still serving the main one: composition) for example, some of them (Result and Maybe) are used to work with different types of errors starting with NullPointerException to arbitrary user-defined ones.

Error handling

When talking about error handling we use a concept of Railway oriented programming. It means that flow of our program has two tracks:

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

  2. Failed one: where something went wrong

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

graph LR S1 -- bind --> S3 S1 -- bind --> F2 S3 -- map --> S5 S5 -- bind --> S7 S5 -- bind --> F6 F2 -- alt --> F4 F4 -- lash --> F6 F4 -- lash --> S5 F6 -- lash --> F8 F6 -- lash --> 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.

Returning execution to the right track

We also support two special methods to work with “failed” values:

Let’s start from the first one: alt method allows to change your error type.

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

Illustration of alt method.

>>> from returns.result import Failure
>>> assert Failure(1).alt(str) == Failure('1')

The second method is lash. It is a bit different. We pass a function that returns another container to it. returns.interfaces.lashable.LashableN.lash() is used to literally bind two different containers together. It can also lash your flow and get on the successful track again:

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

Illustration of lash method.

>>> from returns.result import Result, Failure, Success

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

>>> value: Result[int, Exception] = Failure(ZeroDivisionError())
>>> result: Result[int, Exception] = value.lash(tolerate_exception)
>>> assert result == Success(0)

>>> value2: Result[int, Exception] = Failure(ValueError())
>>> result2: Result[int, Exception] = value2.lash(tolerate_exception)
>>> # => Failure(ValueError())

From typing perspective .alt and .lash are exactly the same as .map and .bind but only work with the second type argument instead of the first one:

from returns.result import Result

first: Result[int, int]
second: Result[int, int]

reveal_type(first.map(str))
# => Result[str, int]

reveal_type(second.alt(str))
# => Result[int, str]

Note

Not all containers support these methods, only containers that implement returns.interfaces.lashable.LashableN and returns.interfaces.altable.AltableN For example, IO based containers and RequiresContext cannot be alted or lashed.

Unwrapping values

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

>>> from returns.result import Failure, Success
>>> from returns.maybe import Some, Nothing

>>> assert Success(1).value_or(None) == 1
>>> assert Some(0).unwrap() == 0
>>> Failure(1).unwrap()
Traceback (most recent call last):
  ...
returns.primitives.exceptions.UnwrapFailedError

>>> Nothing.unwrap()
Traceback (most recent call last):
  ...
returns.primitives.exceptions.UnwrapFailedError

For failing containers you can use returns.interfaces.unwrappable.Unwrapable.failure() to unwrap the failed state:

>>> assert Failure(1).failure() == 1
>>> Success(1).failure()
Traceback (most recent call last):
  ...
returns.primitives.exceptions.UnwrapFailedError

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

Note

Not all containers support these methods, only containers that implement returns.interfaces.unwrappable.Unwrappable. For example, IO based containers and RequiresContext cannot be unwrapped.

Note

Some containers also have .value_or() helper method. Example:

>>> from returns.result import Success, Failure
>>> assert Success(1).value_or(None) == 1
>>> assert Failure(1).value_or(None) is None

Further reading