:::warning Announcements - Help hours today 3:45-5:15 in L037 - A3 pairs assigned, try some tests by today! - Quiz 1 next class Monday, the 24th - Logistics page [here](https://cs.wellesley.edu/~cs251/s25/notes/quiz1.html). ::: :::success Agenda for today - Answering some common questions on A3 - OCaml basics: local lets, conditionals - More language features: floats, strings - Pattern matching - Sum vs. product types - Variant types revisited - Tuples, records - (if time) digging into function types ::: # Some common questions on A3 Unlike in our `calc` example, here, we have multiple possible value types: numbers, booleans, strings, and lambdas. To resolve this, `interp`'s return type is `Value`. ```racket (define-type Value (v-num [value : Number]) (v-str [value : String]) (v-bool [value : Boolean]) (v-fun [param : Symbol] [body : Expr] [env : Env])) ``` Examples of how to get at the underlying data: ```racket > (v-bool #t) (v-bool #t) > (define res (v-bool #t)) > res (v-bool #t) > (define res2 (v-str "alexa")) > res2 (v-str "alexa") > (v-str-value res2) "alexa" ``` Example of a new test: ```racket (test-equal? "string equality" (eval `{str= "hi" "hi"}) (v-bool #true)) ``` # More OCaml basics: things we've seen, in a new context # Local bindings Like in Racket, local bindings also use `let`. In OCaml, the `in` keyword separates the expression being bound from the body: ```ocaml let x = e1 in e2 ``` For example: ```ocaml utop: let x = 3 ;; val x : int = 3 utop: let y = 1 in x * y ;; - : int = 3 ``` If we then try to use `y` outside of that scope: ```ocaml utop: y ;; Error: Unbound value y ``` We can use local bindings inside a top-level function bindings: ```ocaml utop: let f x = let y = 2 in (x * y);; val f : int -> int = <fun> ─( 21:02:50 )─< command 8 >─{ cou utop: f 5 ;; - : int = 10 ``` :::info Note that OCaml does not require parentheses in all function calls - they're only necessary to disambiguate order of operations. Here, in calling `f` and passing `5`, we don't need to group anything with parens. ::: ## Floats Recall that last lecture, we saw that using the `+` operator allowed OCaml to infer that the arguments had type `int`: ```ocaml utop: let g x = 1 + x ;; val g : int -> int = <fun> ``` :::info This implies that `+` can only be used on OCaml integers! How can we add other types? ::: OCaml has a distinct type for `float`s. ```ocaml utop # 5.3 ;; - : float = 5.3 ``` Like the `.` indicating that a constant should be read as a `float`, OCaml's operators on `float` include a `.`: ```ocaml utop # let h x = 1.0 +. x ;; val h : float -> float = <fun> ``` We cannot add integers and floats with this alone, since it violates OCaml's static typing semantics: ```ocaml utop # 1.0 +. 2 ;; Error: This expression has type int but an expression was expected of type float ``` However, we can use an explicit conversion operator: ```ocaml utop # 1.0 +. (float_of_int 2) ;; - : float = 3. ``` :::info This pattern of `type1_of_type2` is used across many OCaml types. ::: :::success To answer a student question: this actually *converts* the integer to the closest floating point representation, rather than simply reinterpreting the bits. ::: Note that we can also leave off the zero: ```ocaml utop # 1. ;; - : float = 1. ``` ## Strings OCaml strings are what you might expect: ```ocaml utop # "hi" ;; - : string = "hi" ``` We cannot add integers and strings: ```ocaml utop # "hi" + 0 ;; Error: This expression has type string but an expression was expected of type int ─( 22:37:01 )─< command 17 >────────{ counter: 0 }─ utop # "1.0" + 0 ;; Error: This expression has type string but an expression was expected of type int ``` However, we can use a conversion: ```ocaml utop # (int_of_string "1") + 0 ;; - : int = 1 ``` We can also append strings using `^`: ```ocaml "1" ^ string_of_int 0 ;; ``` ## Boolean operators OCaml uses infix `&&` and `||`, as well as the keyword `not`. To demonstrate some of these features together: ```ocaml utop # let is_even n = n mod 2 = 0 ;; val is_even : int -> bool = <fun> utop # let avg_ceil ((x: int), (y: int), (ceil: bool)) = ((x + y) / 2) + (if ceil && not (is_even (x + y)) then 1 else 0) ;; val avg_ceil : int * int * bool -> int = <fun> utop # avg_ceil (1, 2, true) ;; - : int = 2 utop # avg_ceil (1, 2, false) ;; - : int = 1 ``` Next, we'll zoom out to look at more complex types that we briefly saw in `#lang plait` but will explore in depth in OCaml. # OCaml compound data types, pattern matching Broadly, whenever we design a new type `t` we need two things: 1. A way to build (“introduce”, “construct”) values of type `t` 2. A way to use (“eliminate”, “destruct”) values of type `t` For example, for the cons `list` type we already previewed: :::info **Type:** `t list` **Build/construct** `[]`, `::` Examples: `bool list` `int list` `int list list` **Use** `List.hd` and `List.tl` (up to now) ::: Note that like in Racket, lists are values. `[]` is the empty list for any type. OCaml writes, “any type of list” as e.g. `‘a list`. This is a _polymorphic_ type, which we'll see a lot of in OCaml. The closest analogy to this may be the generic types in Java you saw in CS230. ### Our first taste of pattern matching Across both Racket and OCaml, we have now seen many recursive functions that (1) have a case for the empty list, then (2) have a case that does something that uses both the first element of the list, and the remaining elements of the list. OCaml provides an extremely convenient syntax for this common form: **pattern matching** We'll see more of this next time, but the basic idea is that you can write the following: ```ocaml let rec f xs = match xs with | [] -> (* base case ... *) | x::xs' -> (* recursive case, can use x for head and xs' for tail *) ``` ## Sum and Product Types: Tuples, Variants We've now seen many of the basic primitive types in OCaml, and a basic structured, homogenous data type of `list`. But how do we _combine_ primitive types to make more complex data representations? Say we have two types: an integer, and a string. We have different ways of combining these depending on the purpose! Broadly, programming language people describe these as either _sum_ or _product_ types. If we think of "the set of all integers" and "the set of all strings", sometimes, we want a datatype that pulls from _both_ of these sets. That is, there are cases where we want to define data composed of both an integer and a string. The possibilities are drawn by thinking about the _product_ (think: multiply the sizes, or cross product); thus, this is a *product type*. The most simple product types in OCaml are _tuples_ (which you have seen before in Python). ### Pairs (2-tuples) We'll start with tuples with two values: also known as _pairs_: :::info **Type:** Pairs (2-tuples) **Build/construct** `(e1, e2)` where `e1`, `e2` are expressions Example types: `(int * int)` `(string * int)` `*` is a type constructor: it builds tuple types out of other types. **Use** Access with: `fst e` and `snd e` *Or* pattern match with: `let (e1, e2) = e`. ::: :::warning Note that `*` means different things when applied to *values* vs. *types*: `3 * 4` is an expression that evaluates to `12`, `int * int` is a type that represents a pair of integers. ::: ### Pair examples For example, we could use a pair to model a student with name and id: ```ocaml utop # let student : string * int = ("alice", 4) ;; val student : string * int = ("alice", 4) (fst student) ;; - : string = "alice" utop # (snd student) ;; - : int = 4 utop # let (name, id) = student in name ;; - : string = "alice" ``` Pairs (and more general tuples) are great for when we want to use multiple types _at once_ in a compound data type. What about cases where we want to use _one of_ multiple different types? ## Sum Types: Variants _Sum_ types have their name because conceptually, going back to out "set of all integers" and "set of all strings" thought experiment, _sum_ types are choices taken from either of the sets. The total number of choices is the sum, rather than the product. We have already seen basic sum types: `plait` variants! OCaml also defines these as variant types: For example, if we want _either_ an integer or a string (maybe because we want an ID that can be either): ```ocaml type int_or_string = | MyInt of int | MyString of string ``` :::info **Type:** Variant Types **Build/construct** Define the type as: ```ocaml type type_name = | Variant1 of ... | Variant2 of ... ``` Where each `...` could be a further compound type. Example types: `int_or_string` Calculator example (below) **Use** Pattern match with: ```ocaml match e with | Variant1 ... -> (* case1 *) | Variant2 ... -> (* case2 *) ``` ::: We can use variant types to reproduce the core of our calculator example from `plait`: ```ocaml (* Define the type MathExpr *) type math_expr = | ENum of int | EAdd of math_expr * math_expr (* Function to evaluate a math expression *) let rec calc expr = match expr with | ENum value -> value | EAdd (x, y) -> (calc x) + (calc y) ``` Note that here, our `math_expr` variant type is recursive! The `EAdd` variant contains a pair of `math_expr`s. Next class, we'll dig deeper into (1) compound data types, and (2) how OCaml represents functions (with one weird trick!)