Lecture 10: Exceptions, Type Checking ---- :::warning Announcements - **Please sit in the first 2 rows of the room**. - `A4: Static Types` due tonight (shorter than usual, extendable to Sunday instead of Saturday) - `A5: Type Checking` out tomorrow - Quiz 1 grading in progress (likely back tomorrow) - Next help hours: - Emily 3-5pm tomorrow - Mine 6:30-8:30pm Monday ::: :::success Agenda for today - Exceptions - Conceptually, `try`/`with` in OCaml - Type checking (A5) - Dynamic vs. static type checking - Type representations - More details for `A5: Type Checking` - Polymorphism (if time) ::: # Exceptions ```ocaml (* New exception without any carried data *) exception NewException (* New exception that carries an int *) exception BadNum of int ``` ## Raising exceptions ```ocaml raise NewException raise (BadNum -1) ``` ```ocaml try e with NewException -> 0 try e with BadNum n -> n ``` ## Examples of using exceptions ```ocaml exception NewException (* Want data passed along with my exception *) exception BadNum of int let divide x y = match y with | 0 -> raise (BadNum 0) (* _ is a wildcard: it matches anything, but creates no binding *) (* like "else" in Racket/plait: fallback case *) | _ -> x / y ``` Running this code gives: ```ocaml (* Normal, non-exception case *) utop # divide 4 2 ;; - : int = 2 (* Exception case *) utop # divide 4 0 ;; Exception: BadNum 0. (* Catch the exception with try/with *) utop # try (divide 4 0) with | BadNum n -> 0 ;; - : int = 0 (* Catching a different exception still raises *) utop # try (divide 4 0) with | NewException -> 0 ;; Exception: BadNum 0. (* But a non-exceptional case is still fine with the "wrong" one *) utop # try (divide 4 2) with | NewException -> 0 ;; - : int = 2 utop # try (divide 4 0) with | NewException -> 999999 ;; Exception: BadNum 0. (* We can use a wildcase if we don't care about the value *) utop # try (divide 4 0) with | BadNum _ -> 999999 ;; - : int = 999999 ``` ## Evaluation and typing rules for `raise` :::info We raise (throw) exceptions with `raise e`. **Type-checking:** - `e` must have type `exn` - Result type is any type you want (!) **Evaluation rules:** - Do not produce a result (!) - Stop with normal control flow - Pass the exception produced by `e` to the nearest try expression on the call stack... (sort of like dynamic scope) ::: ## Evaluation and typing rules for `try/with` :::info `try... with ...` expressions can catch exceptions. **Evaluation rules:** - Evaluate `e1` - But if `e1` raises an exception and that exception matches the pattern, then evaluate `e2` and that is the result - Otherwise, `e1`'s value is the result **Type-checking:** `e1` and `e2` must have the same type and that is the overall type. ::: ## Unit testing for exceptions ```ocaml open Impl (* Basic test *) let%test "basic" = divide 4 2 = 2 (* Test that an exception is raised Conceptually: try <call function under test>, if no exception thrown, test should fail -> false if excepted exception thrown, test should succeed -> true *) let%test "divide should raise BadNum" = try ignore (divide 1 0); false with BadNum 0 -> true ``` # Type checking (Assignment 5!) ## Dynamic vs. Static type checking ## Type checking analysis ### Let's consider some examples Do the following OCaml expressions *type check*? `1 + 2` `1.0 + 2` `(fun x -> x + 1) true` # Assignment 5: simple type checking ``` (lam (x) x) ``` ``` (lam (x : Int) x) ``` # Group exercise :::success Part 1: Design an OCaml recursive variant type to represent the types of values in MiniLangT: numbers, booleans, lists, or functions. Lists should be homogeneous. ::: :::success Part 2: Design an OCaml recursive variant type to represent the following cases of the grammar to an OCaml ``` <expr> ::= <num> | true | false | <var> | (+ <expr> <expr>) | (let ((<id> <expr>)+) <expr>) ``` ::: ## More on type checking for Assignment 5 `(lam ((x : Bool)) x)` `empty` `(empty : Num)` ```ocaml utop # fun x -> x ;; - : 'a -> 'a = <fun> ``` ```ocaml utop # [];; - : 'a list = [] ``` More on this next time!