Eliminating Run Time Errors In OCaml and Haskell

Run Time Versus Compile Time Error

Run time errors are those that happen when the program runs, as opposed to compile time errors which happen when the program compiles.  In a sense, having compile time errors is no big deal, because the errors guarantee that the ill-defined program won’t be running.  The programmer can fix the error before the program runs.  In contrast,  run time errors have an unlimited price tag.  A crashed program could cost revenue lost, if it was a financial application.  Worse still, a crashed program could cost lives in case of medical applications.

In the last post, I demonstrate an OCaml or Haskell program (find_min) that compiles, but throws an exception when the input list is empty.  I’ll use the same example to demonstrate how we can turn run time errors to compile time errors.

Use the Type Checker

Both OCaml and Haskell are statically typed languages, which means that they type check (verify all functions have the correct input types passed to them) at compile-time as opposed to run-time.  We can make use of the advantage of these typed languages to turn the exception (a run time error) to a compile time error.  We can construct a new type that satisfies all type requirements of the function it passes to.  E.g., we can construct a type that is the nonempty list for find_min.  When we pass the empty list to our find_min function, we get a type error at compile time!  E.g., in OCaml:

(* Define the type 'a nonempty_list which enforces that there is at least one element in the list. *)
utop # type 'a nonempty_list = 'a * 'a list;;
type 'a nonempty_list = 'a * 'a list

(* Define find_min to take the type 'a nonempty_list as the input. *)
utop # let find_min (l: 'a nonempty_list) = match l with (hd,tl) -> List.fold_left min hd tl;;
val find_min : 'a nonempty_list -> 'a = <fun>

utop # find_min (1,[2;3;0]);;
- : int = 0

utop # find_min ("strings",["works";"too"]);;
- : string = "strings"

(* find_min now gives a type error with the empty list is passed. *)
utop # find_min [];;
Error: This expression has type 'a list
       but an expression was expected of type
         'b nonempty_list = 'b * 'b list

In Haskell, you can do the equivalent:

--define a new type for the nonempty lists.
type Nonempty_list a = (a,[a])

However, the nonempty list type is already defined for you in Data.List.NonEmpty.  The advantage of using it is that Haskell treats it like lists, and thus you can use all the list operations on NonEmpty lists.

--Import the library that supports NonEmpty
Prelude> import Data.List.NonEmpty

--Define find_min such that its input type is NonEmpty
Prelude Data.List.NonEmpty> find_min = foldr1 min :: (Ord a) => NonEmpty a -> a
Prelude Data.List.NonEmpty> find_min (1:|[2,3,0])
Prelude Data.List.NonEmpty> find_min ("strings":|["works","too"])

--Type error occurs when the input list is empty.
Prelude Data.List.NonEmpty> find_min []

<interactive>:9:10: error:
    • Couldn't match expected type ‘NonEmpty a’ with actual type ‘[a0]’
    • In the first argument of ‘find_min’, namely ‘[]’
      In the expression: find_min []
      In an equation for ‘it’: it = find_min []
    • Relevant bindings include it :: a (bound at <interactive>:9:1)

This works well for programs that don’t use the built-in data type (in this case, lists) to begin with.  E.g., if find_min has to be applied to a list that was from some other output, you will have to turn the list to the nonempty list type.

Handle the Exceptions/Errors

See this for a thorough guide on handling exceptions/errors in OCaml.  See this for Haskell.  One way to handle the error is to use a different output type.  I demonstrate this method using find_min as an example.

Use Option in OCaml or Maybe in Haskell

When the input is a list to begin with, it doesn’t make sense to turn it to the nonempty list type because you still have to specify the case when the list is empty.  In such case, we can use a different output type of find_min.

We can return nil when the input list is empty, using option in OCaml or Maybe in Haskell:

In OCaml:

let fund_min = function hd :: tl -> Some (List.fold_left min hd tl) |_ -> None;;
val fund_min : 'a list -> 'a option = <fun>

utop # find_min [];;
- : 'a option = None

In Haskell:

--In ghci, put multi-line definitions in :{ and :}.  :{ and :} are not needed in a program file.
Prelude> :{
Prelude| find_min [] = Nothing
Prelude| find_min a = Just (foldr1 min a)
Prelude| :}
Prelude> find_min []
Prelude> find_min [1,2,3,0]
Just 0
Prelude> find_min ["strings","works","too"]
Just "strings"

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.