:::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!)