Result Monad (Railway Pattern)
Relevant source files
The following files were used as context for generating this wiki page:
The Result Monad is a foundational pattern in the FLEXT ecosystem used to implement Railway-Oriented Programming (ROP). It provides a structured way to handle operations that can either succeed with a value or fail with an error, avoiding the use of traditional exception-based control flow for expected domain errors. By wrapping outcomes in a Result[T] type (aliased as r), developers can chain operations together in a "track" where successful results continue down the main path and failures short-circuit into an error-handling path.
Sources: examples/README.md:37, tests/integration/test_system.py:28-30, src/flext_core/_protocols/_result_parts/flextprotocolsresult_part_01.py:22-24
Core Architecture and Protocols
The Result implementation is defined through a series of structural protocols that segment the API by concern. This ensures that the public surface of a Result object remains consistent while providing specific interfaces for observation, unwrapping, and monadic composition.
Result State Management
A Result object exists in one of two primary states: success or failure. This state is exposed through a set of read-only properties that allow for inspection without immediate consumption.
| Property | Type | Description |
|---|---|---|
success | bool | True only if the operation succeeded. |
failure | bool | True if the operation failed (not success). |
value | T | The payload (accessible only on success). |
error | str | None | The error message (accessible only on failure). |
error_code | str | None | Structured category code for the error. |
error_data | JsonMapping | None | Additional metadata context for the error. |
exception | BaseException | None | The original exception if the failure was captured from one. |
Sources: src/flext_core/_protocols/_result_parts/flextprotocolsresult_part_01.py:26-68, tests/integration/test_system.py:38-54
Railway Flow Logic
The following diagram illustrates how the Result Monad directs the flow of data through "happy" and "unhappy" tracks.
The diagram shows the short-circuiting nature of the railway pattern where failures bypass subsequent transformations unless specifically recovered.
Sources: tests/integration/test_system.py:84-135, examples/ex_01_flext_result_helpers.py:108-142
Monadic Operations and Composition
Composition is achieved through monadic methods that allow for functional chaining. These methods ensure that domain logic remains separated from error-handling boilerplate.
Transformation and Chaining
map: Transforms the success value using a provided function. If the result is a failure, the transformation is skipped.flat_map: Chains a function that itself returns aResult. This is the primary tool for sequencing fallible operations.flow_through: Sequentially applies multiple result-returning functions to a value.
Sources: src/flext_core/_protocols/_result_parts/flextprotocolsresult_part_03.py:47-50, tests/integration/test_documented_patterns.py:42-83
Error Handling and Recovery
lash: Attempts to recover from a failure by providing a function that returns a newResult.recover: Replaces a failure message with a success value.map_error: Transforms the error string while remaining in a failure state.
Sources: src/flext_core/_protocols/_result_parts/flextprotocolsresult_part_03.py:38-42, examples/ex_01_flext_result_helpers.py:127-142
Side Effects and Observation
tap: Executes a side-effect (like logging) on a success value without changing the value itself.tap_error: Executes a side-effect on a failure message.fold: Reduces the result to a single value by providing handlers for both success and failure cases.
Sources: src/flext_core/_protocols/_result_parts/flextprotocolsresult_part_03.py:32-36, examples/ex_01_flext_result_helpers.py:61-94
Extraction and Unwrapping
To exit the "railway" and retrieve the underlying data, the framework provides several unwrapping strategies.
The sequence diagram demonstrates the difference between strict unwrapping (which may raise) and safe unwrapping with fallbacks.
Sources: src/flext_core/_protocols/_result_parts/flextprotocolsresult_part_01.py:107-133, tests/integration/test_system.py:56-61
Practical Usage Examples
Validating and Transforming Data
The pattern is often used for pipeline processing where each step might fail.
def process_data(input_str: str) -> r[int]:
return (
r[str].ok(input_str)
.map(lambda s: s.strip())
.filter(lambda s: len(s) > 0)
.flat_map(lambda s: r.safe(int)(s)) # Safe wrap of int()
.map(lambda n: n * 2)
)
Sources: tests/integration/test_system.py:173-195, examples/ex_01_flext_result.py:84-86
Resource Management
The with_resource helper manages the lifecycle of a resource within a railway operation, ensuring cleanup occurs regardless of success or failure.
Sources: examples/ex_01_flext_result_helpers.py:38-59
Conclusion
The Result Monad and the Railway Pattern provide a type-safe, predictable framework for error handling in the FLEXT project. By treating failures as data rather than control-flow interruptions, it enables clear, composable, and testable domain logic. This pattern is central to the project's architecture, appearing in everything from low-level utilities to high-level service dispatchers.