Error handling is important but can make an elegant algorithm into a mess.
Railway Oriented Programming (
is used to make error handling elegant and composable.
Consider the simple function
let tryParse s = let b, v = System.Int32.TryParse s if b then Some v else None let f (g : string option) : float option = match g with | None -> None | Some s -> match tryParse s with // Parses string to int | None -> None | Some v when v < 0 -> None // Checks that int is greater than 0 | Some v -> v |> float |> Some // Maps int to float
The purpose of
f is to parse the input
string value (if there is
int. If the
int is greater than
0 we cast it into a
float. In all
other cases we bail out with
Although, an extremely simple function the nested
match decrease readability
ROP observes we have two kind of execution paths in our program
Since the error paths are more frequent they tend to take over the code. We would like that the happy path code is the most visible code path.
An equivalent function
ROP could look like this:
let g (v : string option) : float option = v |> Option.bind tryParse // Parses string to int |> Option.filter ((<) 0) // Checks that int is greater than 0 |> Option.map float // Maps int to float
It looks a lot like how we tend to process lists and sequences in
One can see an
Option<'T> like a
List<'T> that only may contain
Option.bind behaves like
List.pick might be easier to understand).
map handles the error paths and
g only contain the happy
All functions that directly accepts
Option<_> and returns
directly composable with
ROP therefore increases readability and composability.