49
Compilation 2012 More OCaml Jan Midtgaard Aarhus University

Compilation 2012 More OCaml - Computer Science AUcs.au.dk/~mis/dOvs/slides/35d-ocaml-next.pdf · More OCaml 2 OCaml, recap ... Warning 11: this match case is unused. Warning 11:

  • Upload
    letu

  • View
    224

  • Download
    0

Embed Size (px)

Citation preview

Compilation 2012

More OCaml

Jan Midtgaard

Aarhus University

2 More OCaml

OCaml, recap

By now you’ve installed OCaml and written/sent your first

expression to the toplevel

Last time we wrote some basic OCaml expressions

following the below grammar:

exp value (ints, bools, chars, strings,...)

| exp + exp | exp - exp | … (binary operations)

| - exp (unary minus)

| ( exp ) (parenthesized exps)

3 More OCaml

Top-level let bindings

In ML one can bind the value of an expression to a name:

let id = exp

For example:

let x = 3;;

let y = 4;;

x + y;;

Important note: this is not an assignment!

An assignment has state, i.e., a little piece of

memory that can (and will) change under your

feet…

4 More OCaml

Nested let bindings

One can also locally bind a value to a name locally within an expression:

let id = exp in exp

Confusingly this is also expressed with the let

keyword(!)

For example:

let x = 3 in x * x * x gives 27

but afterwards x is no longer visible:

# x+x;;

Error: Unbound value x

5 More OCaml

OCaml syntax, recap

A grammar can formally distinguish top-level lets from the

nested, expression-level lets :

topdecl exp

| let id = exp (top-level let)

exp id

| value

| exp + exp | exp - exp | …

| - exp

| ( exp )

| let id = exp in exp (expr-level let)

6 More OCaml

Functions (are fun) (1/2)

Functions are written with the fun keyword:

fun id … id -> exp

For example: fun x -> x * x

has the type: int -> int

We can bind the function value to a name:

let square = fun x -> x * x

and call it:

# square 4;;

- : int = 16

#

7 More OCaml

Functions (are fun) (2/2)

It is so common to bind a function value to a name that there is a short hand notation:

let funname id … id = exp

For example: let square x = x * x

One can also locally define functions with similar short hand notation:

let funname id … id = exp in exp

For example: let quadruple n =

let double m = m + m in

double (double n)

8 More OCaml

Recursive functions

Recursive functions are explicitly marked as such with the rec keyword:

let rec funname id … id = exp

For example:

let rec fac n =

if n = 0

then 1

else n * fac (n - 1)

To which OCaml responds:

val fac : int -> int = <fun>

9 More OCaml

Mutually recursive functions

Recursive functions that call each other should be declared simultaniously with and.

For example: let rec is_even n =

if n = 0

then true

else is_odd (n - 1)

and is_odd n =

if n = 0

then false

else is_even (n - 1)

to which OCaml responds:

val is_even : int -> bool = <fun>

val is_odd : int -> bool = <fun>

10 More OCaml

Pattern matching (1/5)

One typically destructs data using pattern

matching:

match exp with

| pattern -> exp

| pattern -> exp

For example:

let bool_to_string b = match b with

| true -> "true"

| false -> "false"

11 More OCaml

Pattern matching (2/5)

OCaml’s pattern matching compiler helps ensure

that you didn’t miss a case:

let is_valid_bool s = match s with | "true" -> true

| "false" -> true

un>

12 More OCaml

Pattern matching (2/5)

OCaml’s pattern matching compiler helps ensure

that you didn’t miss a case:

let is_valid_bool s = match s with | "true" -> true

| "false" -> true

Warning 8: this pattern-matching is not

exhaustive.

Here is an example of a value that is not

matched:

""

val is_valid_bool : string -> bool = <fun>

13 More OCaml

Pattern matching (3/5)

The wildcard pattern can be used to catch

”default” behavior:

which will satisfy OCaml:

The exhaustiveness check is your friend.

Do not use wildcards everywhere just to “make it shut up”.

let is_valid_bool s = match s with

| "true" -> true

| "false" -> true

| _ -> false

val is_valid_bool : string -> bool = <fun>

14 More OCaml

Pattern matching (4/5)

Beware that the pattern order now matters:

let is_valid_bool s = match s with

| _ -> false

| "true" -> true

| "false" -> true

15 More OCaml

Pattern matching (4/5)

Beware that the pattern order now matters:

let is_valid_bool s = match s with

| _ -> false

| "true" -> true

| "false" -> true

Warning 11: this match case is unused.

Warning 11: this match case is unused.

val is_valid_bool : string -> bool = <fun>

16 More OCaml

Pattern matching (5/5)

Patterns can also bind variables which will be

visible within the pattern’s right-hand-side:

to which OCaml responds:

let rec fac n = match n with

| 0 -> 1

| n -> n * fac (n - 1)

val fac : int -> int = <fun>

17 More OCaml

Tuples

Tuples are one way to combine types to build new ones:

type point3d = int * int * int

which declares point3d as a short hand for int triples

OCaml will infer tuple types (they don’t need to be declared):

One can project data from pairs with fst and snd:

# let mypair = (1,2);;

val mypair : int * int = (1, 2)

# snd mypair;;

- : int = 2

18 More OCaml

Tuple matching

One can also pattern match on tuple types using let:

let distance_from_origo p =

let (x,y) = p in

let sqr_dist = (x * x) + (y * y) in

sqrt (float_of_int sqr_dist)

for which OCaml infers the type:

Alternatively one can pattern match directly in the function

header:

let distance_from_origo' (x,y) =

let sqr_dist = (x * x) + (y * y) in

sqrt (float_of_int sqr_dist)

val distance_from_origo : int * int -> float = <fun>

19 More OCaml

Lists

Lists are created inductively from the empty list [] and the

cons operator ::

In Java we would (probably) write this as List<Integer>

As a short hand one can also write list literals with square

brackets and semicolon as element separator:

One can concatenate lists with @:

# mylist@mylist;;

- : int list = [1; 2; 3; 1; 2; 3]

# let mylist' = [0;1;2;3];;

val mylist' : int list = [0; 1; 2; 3]

# let mylist = 1::2::3::[];;

val mylist : int list = [1; 2; 3]

20 More OCaml

Lists, polymorphically

We can now write structurally recursive functions

over lists:

let rec length l = match l with

| [] -> 0

| elem::elems -> 1 + length elems

For which OCaml will infer the polymorphic type:

The corresponding generic Java method would accept a List<X> and return a Java int

val length : 'a list -> int = <fun>

21 More OCaml

Data types (1/3)

One can also form new types by creating disjoint unions

(aka algebraic data types)

They represent a variant (like an enum in C or Java)

with a limited number of choices.

For example: type mybool =

| Mytrue

| Myfalse

22 More OCaml

Data types (1/3)

One can also form new types by creating disjoint unions

(aka algebraic data types)

They represent a variant (like an enum in C or Java)

with a limited number of choices.

For example: type mybool =

| Mytrue

| Myfalse

Note: the constructors have to be upper case

Values are created with the constructors:

# Mytrue;;

- : mybool = Mytrue

23 More OCaml

Data types (2/3)

We can process the data types using pattern matching

There is typically one case per constructor

For example:

let mybool_to_bool mb = match mb with

| Mytrue -> …

| Myfalse -> …

24 More OCaml

Data types (2/3)

We can process the data types using pattern matching

There is typically one case per constructor

For example:

let mybool_to_bool mb = match mb with

| Mytrue -> true

| Myfalse -> false

In this way we let the types guide us

25 More OCaml

Data types (3/3)

Data types can also carry data:

type card =

| Clubs of int

| Spades of int

| Hearts of int

| Diamonds of int

which we also extract by pattern matching:

let card_to_string c = match c with

| Clubs i -> …

| Spades i -> …

| Hearts i -> …

| Diamonds i -> …

26 More OCaml

Data types (3/3)

Data types can also carry data:

type card =

| Clubs of int

| Spades of int

| Hearts of int

| Diamonds of int

which we also extract by pattern matching:

let card_to_string c = match c with

| Clubs i -> (string_of_int i) ^ " of clubs"

| Spades i -> (string_of_int i) ^ " of spades"

| Hearts i -> (string_of_int i) ^ " of hearts"

| Diamonds i -> (string_of_int i) ^ " of diamonds"

27 More OCaml

Polymorphic data types

OCaml comes with a builtin option type

type 'a option =

| None

| Some of 'a

Note: option is polymorphic – it can carry any kind of data

This is handy for signalling “data is there / data isn’t there”

For example:

let first_elem l = match l with

| [] -> …

| e::es -> …

28 More OCaml

Polymorphic data types

OCaml comes with a builtin option type

type 'a option =

| None

| Some of 'a

Note: option is polymorphic – it can carry any kind of data

This is handy for signalling “data is there / data isn’t there”

For example:

let first_elem l = match l with

| [] -> None (*no first element!*)

| e::es -> …

29 More OCaml

Polymorphic data types

OCaml comes with a builtin option type

type 'a option =

| None

| Some of 'a

Note: option is polymorphic – it can carry any kind of data

This is handy for signalling “data is there / data isn’t there”

For example:

let first_elem l = match l with

| [] -> None

| e::es -> Some e (*first elem present*)

30 More OCaml

Polymorphic data types

OCaml comes with a builtin option type

type 'a option =

| None

| Some of 'a

Note: option is polymorphic – it can carry any kind of data

This is handy for signalling “data is there / data isn’t there”

For example:

let first_elem l = match l with

| [] -> None

| e::es -> Some e (*first elem present*)

for which OCaml infers a polymorphic type:

val first_elem : 'a list -> 'a option = <fun>

31 More OCaml

Example: arithmetic expressions (1/4)

Here’s a data type representing arithmetic expressions close

to what you’ve seen in dProgSprog:

type aexp =

| Lit of int

| Plus of aexp * aexp

| Times of aexp * aexp

Let’s build a little tree data structure representing 1+2*3:

# let mytree = Plus (Lit 1, Times (Lit 2, Lit 3));;

val mytree : aexp = Plus (Lit 1, Times (Lit 2, Lit 3))

32 More OCaml

Example: arithmetic expressions (2/4)

Inductive datatypes and recursive functions make a powerful

cocktail:

let rec interpret ae = match ae with

| Lit i -> …

| Plus (ae0, ae1) ->

| Times (ae0, ae1) ->

33 More OCaml

Example: arithmetic expressions (2/4)

Inductive datatypes and recursive functions make a powerful

cocktail:

let rec interpret ae = match ae with

| Lit i -> i

| Plus (ae0, ae1) ->

| Times (ae0, ae1) ->

34 More OCaml

Example: arithmetic expressions (2/4)

Inductive datatypes and recursive functions make a powerful

cocktail:

let rec interpret ae = match ae with

| Lit i -> i

| Plus (ae0, ae1) ->

let v0 = interpret ae0 in

| Times (ae0, ae1) ->

35 More OCaml

Example: arithmetic expressions (2/4)

Inductive datatypes and recursive functions make a powerful

cocktail:

let rec interpret ae = match ae with

| Lit i -> i

| Plus (ae0, ae1) ->

let v0 = interpret ae0 in

let v1 = interpret ae1 in

| Times (ae0, ae1) ->

36 More OCaml

Example: arithmetic expressions (2/4)

Inductive datatypes and recursive functions make a powerful

cocktail:

let rec interpret ae = match ae with

| Lit i -> i

| Plus (ae0, ae1) ->

let v0 = interpret ae0 in

let v1 = interpret ae1 in

v0 + v1

| Times (ae0, ae1) ->

37 More OCaml

Example: arithmetic expressions (2/4)

Inductive datatypes and recursive functions make a powerful

cocktail:

let rec interpret ae = match ae with

| Lit i -> i

| Plus (ae0, ae1) ->

let v0 = interpret ae0 in

let v1 = interpret ae1 in

v0 + v1

| Times (ae0, ae1) ->

let v0 = interpret ae0 in

let v1 = interpret ae1 in

v0 * v1

38 More OCaml

Example: arithmetic expressions (2/4)

Inductive datatypes and recursive functions make a powerful

cocktail:

let rec interpret ae = match ae with

| Lit i -> i

| Plus (ae0, ae1) ->

let v0 = interpret ae0 in

let v1 = interpret ae1 in

v0 + v1

| Times (ae0, ae1) ->

let v0 = interpret ae0 in

let v1 = interpret ae1 in

v0 * v1

Now let’s run the thing: # interpret mytree;;

- : int = 7

39 More OCaml

Example: arithmetic expressions (3/4)

By a similar traversal we can serialize the tree into a string:

let rec exp_to_string ae = match ae with

| Lit i ->

| Plus (ae0, ae1) ->

| Times (ae0, ae1) ->

40 More OCaml

Example: arithmetic expressions (3/4)

By a similar traversal we can serialize the tree into a string:

let rec exp_to_string ae = match ae with

| Lit i ->

string_of_int i

| Plus (ae0, ae1) ->

let s0 = exp_to_string ae0 in

let s1 = exp_to_string ae1 in

"(" ^ s0 ^ "+" ^ s1 ^ ")"

| Times (ae0, ae1) ->

let s0 = exp_to_string ae0 in

let s1 = exp_to_string ae1 in

"(" ^ s0 ^ "*" ^ s1 ^ ")"

41 More OCaml

Example: arithmetic expressions (3/4)

By a similar traversal we can serialize the tree into a string:

let rec exp_to_string ae = match ae with

| Lit i ->

string_of_int i

| Plus (ae0, ae1) ->

let s0 = exp_to_string ae0 in

let s1 = exp_to_string ae1 in

"(" ^ s0 ^ "+" ^ s1 ^ ")"

| Times (ae0, ae1) ->

let s0 = exp_to_string ae0 in

let s1 = exp_to_string ae1 in

"(" ^ s0 ^ "*" ^ s1 ^ ")"

And again run the thing: # exp_to_string mytree;;

- : string = "(1+(2*3))"

42 More OCaml

Example: arithmetic expressions (4/4)

Now suppose we have a little target language for a simple

stack machine:

type inst =

| Push of int

| Add

| Mult

We can now write a compiler from arithmetic expressions

into this type by a similar traversal…

43 More OCaml

Example: arithmetic expressions (4/4)

Again there are three cases to consider:

let rec compile ae = match ae with

| Lit i ->

| Plus (ae0, ae1) ->

| Times (ae0, ae1) ->

44 More OCaml

Example: arithmetic expressions (4/4)

Again there are three cases to consider:

let rec compile ae = match ae with

| Lit i ->

[Push i]

| Plus (ae0, ae1) ->

let is0 = compile ae0 in

let is1 = compile ae1 in

is0 @ is1 @ [Add]

| Times (ae0, ae1) ->

let is0 = compile ae0 in

let is1 = compile ae1 in

is0 @ is1 @ [Mult]

45 More OCaml

Example: arithmetic expressions (4/4)

Again there are three cases to consider:

let rec compile ae = match ae with

| Lit i ->

[Push i]

| Plus (ae0, ae1) ->

let is0 = compile ae0 in

let is1 = compile ae1 in

is0 @ is1 @ [Add]

| Times (ae0, ae1) ->

let is0 = compile ae0 in

let is1 = compile ae1 in

is0 @ is1 @ [Mult]

# compile mytree;;

- : inst list = [Push 1; Push 2; Push 3; Mult; Add]

46 More OCaml

References and side-effects

Assignment is available in OCaml

You allocate a mutable cell in memory with ref:

let mycell = ref 0

One can assign a new value with :=

mycell := 42

And read off the content by dereferencing the address:

The standard library also comes with functions incr and

decr for incrementing or decrementing an integer

reference, respectively

# !mycell;;

- : int = 42

47 More OCaml

Records

Records are a viable alternative to tuples as they name

each entry with a label

Records need to be declared up front:

type identifier = { pos : int * int;

id : string }

After which record values can be created:

let someid = { pos = (2,3);

id = "x" }

Entries are looked up using the familiar dot syntax: let error unknownid =

print_endline (unknownid.id ^ " is not defined")

# error someid;;

x is not defined

48 More OCaml

Exceptions

Exceptions are declared with the exception keyword:

exception Darn_list_is_empty

They are created with the constructor and thrown with raise:

let first_elem' l = match l with

| [] -> raise Darn_list_is_empty

| e::es -> e

Finally they are handled with the try/with construct:

try first_elem' []

with Darn_list_is_empty -> 0

The standard library

OCaml includes a decent standard library:

http://caml.inria.fr/pub/docs/manual-ocaml/libref/index.html

All bindings in the module Pervasives are

available in the top-level.

Many of the functions we have covered (and

more) come from Pervasives - so have a look

49 More OCaml