Error handling

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

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

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

def try_add (n : i32) (path : String) =
    let m =
        let! text = try_read_file path
        let! number = i32.try_parse text
        number

    n + m |> 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 (n : i32) (path : String) =
    let m =
        let text = case try_read_file 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

    n + m |> Some

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

type ParseError = String

type Error =
    | t@ FileError
    | t@ ParseError

def try_add (n : i32) (path : String) =
    let m =
        let! text = try_read_file path
        let! number = let error = ParseError text
                      i32.try_parse text |> to_result error
        number

    n + m |> Result/Ok

Option is converted to Result using the to_result method.