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 arbitary user-defined ones.
When talking about error handling we use a concept of Railway oriented programming. It mean that our code can go on two tracks:
Successful one: where everything goes perfectly: HTTP requests work, database is always serving us data, parsing values does not failed
Failed one: where something went wrong
We can switch from track to track: we can fail something or we can fix the situation.
We also support two special methods to work with “failed”
types like Failure
:
rescue()
is the opposite of bind
method
that works only when container is in failed state
fix()
transforms error to value (failure became success)
that works only when container is in failed state,
is the opposite of map
method
alt()
transforms error to another error
that works only when container is in failed state,
is the opposite of map
method
fix
can be used to fix some fixable errors
during the pipeline execution:
>>> from returns.result import Failure, Result
>>> def double(state: int) -> float:
... return state * 2.0
>>> result: Result[str, float] = Failure(1).alt(double)
>>> str(result)
'<Failure: 2.0>'
>>> result: Result[float, int] = Failure(1).fix(double)
>>> str(result)
'<Success: 2.0>'
rescue
should return one of Success
or Failure
types.
It can also rescue your flow and get on the successful track again:
>>> 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.rescue(tolerate_exception)
>>> str(result)
'<Success: 0>'
>>> value2: Result[int, Exception] = Failure(ValueError())
>>> result2: Result[int, Exception] = value2.rescue(tolerate_exception)
>>> # => Failure(ValueError())
Note:
Not all containers support these methods.
``IO`` and ``RequiresContext`` cannot be fixed, alted, or rescued.
And we have two more functions to unwrap inner state of containers into a regular types:
.value_or
returns a value if it is possible, returns default_value
otherwise
.unwrap
returns a value if it is possible, raises UnwrapFailedError
otherwise
>>> from returns.result import Failure, Success
>>> from returns.maybe import Some, Nothing
>>> Success(1).value_or(None)
1
>>> Some(0).unwrap()
0
>>> Failure(1).value_or(100)
100
>>> 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 .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.
``IO`` based containers and ``RequiresContext`` cannot be unwrapped.