Error handling

def try_read_file (path : String) : Result<String, String> =
    ...

object i32
    def try_parse (s : String) : Option<i32> =
        ...

Result<String, String> contains either text from a file in Ok<String>, or Error<String> with an error message.

def try_add (lhs_path : String) (rhs : i32) =
    let lhs =
        let! text = try_read_file lhs_path
        let! number = i32.try_parse text
        number

    lhs + rhs |> Some

The text and number bindings are of type String and i32, taking into account only successful invocations of the try_read_file and try_parse functions.

Code without let! is shown below.

def try_add (lhs_path : String) (rhs : i32) =
    let lhs =
        let text = case try_read_file lhs_path of
            Result/Ok text -> text
            Result/Error _ -> return None

        let number = case i32.try_parse text of
            Some number -> number
            None -> return None

        number

    lhs + rhs |> Some

The error message from try_read_file can be propagated to the caller if try_add returns Result<i32, String> instead of Option<i32>.

def try_add (lhs_path : String) (rhs : i32) =
    let lhs =
        let! text = try_read_file lhs_path
        let! number = i32.try_parse text |> to_result "cannot parse $text"
        number

    lhs + rhs |> Result/Ok

Option is converted to Result using the to_result method.