Skip to content

Instantly share code, notes, and snippets.

@mrange
Last active October 3, 2016 09:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mrange/e0e599badb711015ea13789b52d1f881 to your computer and use it in GitHub Desktop.
Save mrange/e0e599badb711015ea13789b52d1f881 to your computer and use it in GitHub Desktop.
Option Module enables Railway Oriented Programming in F#

Error handling is important but can make an elegant algorithm into a mess. Railway Oriented Programming (ROP) is used to make error handling elegant and composable.

Consider the simple function f:

    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 Some) into an int. If the int is greater than 0 we cast it into a float. In all other cases we bail out with None.

Although, an extremely simple function the nested match decrease readability significantly.

ROP observes we have two kind of execution paths in our program

  1. Happy path - Will eventually compute Some value
  2. Error path - All other paths produces None

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 g using 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 F#.

One can see an Option<'T> like a List<'T> that only may contain 0 or 1 element where Option.bind behaves like List.pick (conceptually Option.bind maps better to List.collect but List.pick might be easier to understand).

bind, filter and map handles the error paths and g only contain the happy path code.

All functions that directly accepts Option<_> and returns Option<_> are directly composable with |> and >>.

ROP therefore increases readability and composability.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment