- Logistics:
- Next lecture will be online, along with Steven’s office hours this week due to travel
- Next homework released today, due next Wednesday (March 20)
- Jack’s office hour shift (see Piazza)
- Lots of course schedule updates, including all subsequent quiz timings
OCaml for Plait Programmers
- What is OCaml?
- Created in 1996, primarily developed by Xavier Leroy. Based on ML, which first appeared in 1973
- An industrial-grade functional programming-language
- Highly successful in a number of industry applications, in particular compiler writing, development of formal verification tools, and (somewhat surprisingly) finance
- Why switch from Plait to OCaml?
- It is important to explore more language design space and develop languages in a new host semantics
- OCaml is a useful tool you may use one day
- OCaml is very similar in semantics to Plait (it is a functional, strongly-typed, eagerly-evaluated language), but has (1) different syntax which makes it easier to write certain programs (and harder to write others!), and (2) industrial-grade language features that make it suitable for use on practical large-scale projects such as a package manager and module system.
- Some tips:
- Some amount of self-education is expected for this module: you should be well-equiped to teach yourself a new language after studying how to implement languages.
- This online textbook is an excellent resource and covers most of what you will need to know about OCaml.
- When in doubt, there are many OCaml resources online; we will cover basics in class, but you are expected to do some amount of learning on your own when you get stuck or encounter a feature you don’t understand. This is just like in the “real world”. OCaml is a much bigger language than Plait, so it will take some time to get used to it.
Programming in OCaml
- You can follow along yourself with today’s lecture using this online OCaml environment
Basic data and syntax
- Like Plait, OCaml is an expression-based functional programming language
- OCaml’s type-system is very similar to Plait’s: it has many analogous features
- Unlike Plait, OCaml is not an s-expression-based language: this will take some getting used to!
- All the following code is run in the top-level (i.e., in the REPL). In OCaml, top-level expressions are terminated with a double semicolon
;;
- Here we cover the basic usage of integers, strings, and Boolean values:
(* comments are placed inside parenthesis with stars like this *)
(* unlike Plait, number literals are integers *)
> 5 + 5;;
- : int = 10
(* variables are declared using let *)
> let x = 10;;
- val x : int = 10
(* you can also use the `let x = e1 in e2` form to give `x` local scope *)
> let x = 10 in (x + x);;
- : int = 20
(* adding is as expected, but using infix notation instead of s-expressions *)
> x + x;;
- : int = 20;
(* evaluation order can be disambiguated using parenthesis *)
> (10 - 4) + 20;;
- : int = 26
(* we compare for equality using = *)
> 4 = 5;;
- : bool = false
(* inequality is <> *)
> 4 <> 5;;
- : bool = true
(* standard Boolean operations are supported *)
> (true && false) || (not true);;
- : bool = false
(* like Plait, OCaml has strings *)
> "hello";;
- : string = "hello"
(* we can compare strings for equality, just like Booleans *)
> "hello" = "world";;
- : bool = false
(* OCaml's if expression looks a lot like Plait's *)
> if true then 10 else 25;;
- : int = 10
(* like Plait, OCaml will complain if you use the wrong types *)
> if 25 then 10 else 25;;
Error: This expression has type int but an expression was expected of type
bool because it is in the condition of an if-statement
(* like Plait's Void type, OCaml has a unit type and unit value *)
> ();;
- : unit = ()
Defining and calling functions
- OCaml has a very different syntax from Plait for defining and calling functions
- Like Plait, OCaml has a few distinct ways of defining a function:
(* we declare functions using the syntax `let <funcname> <arg1> <arg2> ... <argn> = <body>` *)
> let double x = x + x;;
- val double : int -> int = <fun>
(* notice OCaml's type signature is quite similar to Plait's: this defines a
function of type int -> int *)
(* we can declare functions with more than 1 argument *)
> let myadd x y = x + y;;
val myadd : int -> int -> int = <fun>
(* like Plait, OCaml gives you syntax for annotating the type of a function: *)
> let myaddwithtypes (x:int) (y:int) : int = x + y;;
val myaddwithtypes : int -> int -> int = <fun>
(* we call functions by using the syntax <funcname> <arg1> <arg2> *)
> double 10;;
- : int = 20
(* to use more than 1 argument, pass them in one at a time *)
> myadd 10 20;;
- : int = 30
(* OCaml has a slightly unusual feature: if you call a function without all of its
arguments, it will return a partially-evaluated version of that function.
This is called *currying*, and it's best illustrated via example: *)
> let add10 = myadd 10;;
val add10 : int -> int = <fun>
(* this defines an add10 function that adds 10 to whichever argument we give it! *)
> add10 20;;
- : int = 30
- Like Plait, OCaml provides numerous library functions for manipulating built-in datatypes
(* OCaml provides functions to manipulate strings. To access them, we use the string module.
See https://v2.ocaml.org/api/String.html for the complete documentation on what you can do
with strings. Examples: *)
(* String concatenation. Notice how we prepend these function calls with "String". In OCaml,
library functions are often behind a namespace and accessed using this syntax. *)
> String.cat "hello" "world";;
- : string = "helloworld"
> String.length "hello";;
- : int = 5
> String.get "hello" 0;;
- : char = 'h'
- Like Plait, OCaml has first-class (or anonymous) functions, declared with the
fun <arg> -> <body>
syntax:
(* declare an identity function and call it with the argument 10 *)
> (fun x -> x) 10;;
- : int = 10
(* we can declare anonymous functions that take more than 1 argument
straightforwardly: *)
> (fun x y -> x + y) 10 20;;
- : int = 30
(* we can pass functions as arguments. Note: here we encounter our first instance
of OCaml's syntax being ambiguous. In OCaml, we need to add parenthesis here to
disambiguate the evaluation order, whereas in Plait this was unnecessary due
to the s-expression-based syntax. *)
> let calltwice f x = f (f x);;
val calltwice : ('a -> 'a) -> 'a -> 'a = <fun>
> calltwice (fun x -> 2 * x) 10;;
- : int = 40
(* what happens if we don't include those parenthesis? OCaml will give us a
(fairly uninformative) type error: *)
> let calltwicebad f x = f f x;;
Error: This expression has type 'a -> 'b -> 'c
but an expression was expected of type 'a
The type variable 'a occurs inside 'a -> 'b -> 'c
(* by default, function calls bind "to the left", so `f f x` parses to `(App (App f f) x)`, which
doesn't type-check. If in doubt, add a few parenthesis; unlike in Plait, parenthesis
have no semantic meaning in OCaml other than disambiguating evaluation order. *)
- Recursive functions are defined in OCaml similar to Plait, except we must use the
let rec
form:
> let rec sum x = if x <= 0 then 0 else x + sum (x - 1);;
- val sum : int -> int = <fun>
> sum 5;;
- : int = 15
Lists and pattern matching
- OCaml has very nice syntax for defining and manipulating lists:
(* a list constant is created using the syntax [l0; l1; l2; ...] *)
> [1; 2; 3];;
- : int list = [1; 2; 3]
(* the empty list is simply [] *)
> [];;
- : 'a list = []
(* just like in Plait, lists must be homogeneous types *)
> [1; 2; true];;
Error: This expression has type bool but an expression was expected of type int
(* in OCaml, one uses `::` instead of `cons` to prepend an element to a list: *)
> 1 :: [2; 3];;
- : int list = [1; 2; 3]
(* notice: in OCaml, a list that has integers in it has type `int list`; the
ordering takes some getting used to. *)
(* just like in Plait, we use pattern matching to handle doing interesting
recursive operations *)
> let rec sum_list l =
match l with
| [] ->
(* this arm triggers when l = [] *)
0
| x::xs ->
(* this arm triggers when l = x::xs where x is the head and xs is the rest
of the list *)
x + (sum_list xs)
- val sum_list : int list -> int = <fun>
> sum_list [1; 2; 3; 4;];;
- : int = 10
(* Pattern matching in OCaml is much more powerful than type-case in Plait.
For instance, we can put constants in patterns: *)
> let begins_with_0 l =
match l with
| 0::rest ->
(* this arm triggers if the first element of
the list is 0, and binds the rest of the list to `rest` *)
true
| _ -> false (* the default value in OCaml is `_` *)
> begins_with_0 [0; 1; 2];;
- : bool = true
> begins_with_0 [1; 1; 2];;
- : bool = false
In-class exercises:
- Write a function
is_sorted l
of typeint list -> bool
that istrue
if the list is sorted from least to greatest element andfalse
otherwise. - Write a function
odd_index l
of typeint list -> int list
that returns all the odd-index elements in a list. For example,every_other [1; 2; 3; 4]
would return[2; 4]
. You may need a helper function.