Commit a0315672 authored by Pierre Letouzey's avatar Pierre Letouzey
Browse files

Day 2 (in English)

parent ee7faca4
......@@ -263,4 +263,147 @@ Question: devise a situation of over-application, i.e. a function receiving more
## Day 2 : OCaml data structures
The impatients may have a look at the second half of last year's [notes](init-prog-ocaml-fr.md) (in French).
## OCaml Pairs
A pair `(a,b)` regroups two elements `a` and `b`. These two elements may be of different types (or not).
The type of this pair is written `τ * σ` when types of `a` and `b` are respectively `τ` and `σ`.
OCaml also provides triplets, quadruplets, n-uplets, all built on the same principle, for instance `(1,2,true)` is a `int * int * bool`.
For using a pair, either project it (using predefined functions `fst` et `snd`),
or access all components at once (via a syntax such as `let (x,y) = ... in ...`).
Example : computing two following Fibonacci numbers efficiently.
```ocaml
let fibs n =
if n = 0 then (1,1)
else
let (a,b) = fibs (n-1) in
(b, a+b)
let fib n = fst (fibs n)
```
Note: the Curry-Howard isomorphism mentionned last week can be extended to pairs.
Actually the typing rules of pairs `(a,b)` and of projections `fst` and `snd` mimic the logical rules introducing and eliminating conjunctions.
#### Curried and uncurried functions.
If an OCaml function has several arguments, the habit is rather to separate these arguments as seen last week : `fun arg1 arg2 arg3 -> ...`.
This leads to a type of the form `typ_arg1 -> typ_arg2 -> ... -> typ_res`. This kind of function is said *curried* (after logician Haskell Curry).
But another style is to regroup all arguments in a n-uplet : `fun (arg1,arg2,...) -> ...`.
This leads to a type of the form `typ_arg1 * typ_arg2 * ... * typ_argn -> typ_res`. This function is said *uncurried*. In this case, the function cannot be partially applied easily. Conversely, it might sometimes be convenient to treat all these arguments as a unique one.
#### Exercise : let's curry and uncurry
Question : write a function `curry` which converts a function of type `'a*'b -> 'c` into a function of type `'a -> 'b -> 'c`. Write the converse `uncurry` function.
## OCaml Lists
An OCaml list is a structure regrouping many elements of the same type. Example: `[1;2;3;2]` is an `int list`.
A list is an ordered structure : the positions of elements in a list are relevant, `[2;1]` is not the same than `[1;2]` nor `[1;2;1]`.
A list may be of arbitrary length, starting from 0 (the empty list is `[]`).
The basic constructors of lists are the empty list `[]` and the concatenation `x::l` of an extra element `x` on the left of an existing list `l`.
Note that the `[1;2;3;4]` syntax is just a shortcut for `1::(2::(3::(4::[])))`.
For analyzing a list, there exist predefined functions `List.hd` et `List.tl` (for *head* and *tail*), but favor instead a `match...with` syntax dealing both with the `[]` case and the `x::l` case.
The lists (just as pairs earlier, and trees below) are *immutables* structures : when a list has been built, its elements cannot been modified anymore (unlike for instance *arrays* in OCaml or other programming languages). But it possible to form new lists by reusing older ones (in whole or in right subparts).
The head of a list (its leftmost element) can be accessed in constant time, just as a `x::l` operation. On the contrary, the bottom of a list (its rightmost element) can only be accessed after visiting the whole list, hence in a linear time (proportional to the list length).
#### Exercise : retrieve some usual functions on lists
- Recode a length function on lists (already provided by OCaml as `List.length`).
- Recode the concatenation of two lists (already provided by OCaml as operator `@`).
- Recode the concatenation of two lists that reverses the first one on the fly (cf `List.rev_append`). For instance `rev_append [1;2] [3;4] = [2;1;3;4]`.
- Recode a reverse function on lists (a.k.a. mirror), cf. `List.rev`. If your solution as a quadratic complexity, try to find a linear solution.
- Recode the filter function `List.filter`. From a boolean test function `f` and a list `l`, `List.filter f l` keeps only the elements `x` of `l` such that `f x`.
- Recode `List.map`, which apply a function `f` on all elements of a list.
Optionally, consult documentation for iterators `List.fold_left` and `List.fold_right` and try to recode them.
Beware, when using large lists (more than 30000 elements), some functions may fail with `Stack overflow` error. To avoid that, consider using the *tail rercusive* style (see for instance `rev_append` above) ... ou switch to some more clever structure (trees below, see also arrays).
## Trees
No predefined trees in OCaml (too many variants would be interesting, depending on your intentions). Luckily, OCaml allows to easily define our own custom *algebraic types*.
For instance, here is a tree datatype with elements of type `'a` decoring each binary node:
```ocaml
type 'a tree =
| Node of 'a * 'a tree * 'a tree
| Leaf
let mytree = Node (1, Node (2,Leaf,Leaf), Leaf)
```
For analyzing a tree, once again a `match...with` syntax is very convenient
(or the `function` keyword, which amounts to a `fun` followed by a `match`).
#### Exercise : some usual functions on trees
- Write the `size` and `depth` functions on trees
- Write a `tree2list` function flattening a tree into the corresponding list. Also known as infix tree traversal.
When elements are sorted from left to right during an infix tree traversal, such trees are called Binary Search Trees (BST or "arbre binaire de recherche" (ABR) in French). Using BST may greatly improve the complexity of usual operations.
#### Exercise : binary search trees
- Write a `search` function checking whether some particular element is in a BST. Proceed by dichotomy.
- Write a `is_bst` function checking whether a tree is indeed a BST. Which complexity could we attain ?
If the tree is shallow (i.e. of low depth, i.e. complete or almost complete), the `search` function is logarithmic w.r.t. the tree size.
All the challenge is to keep BST trees as shallow as possible, even when adding extra elements in them. See for instance Red-Black trees or AVL trees.
OCaml provides by default a library of efficient set structures and operations, cf. the `Set` module.
## OCaml Algebraic Types : design your own types
Besides trees, we can continue defining algebraic types fulfilling specific needs. For instance, here is a micro-language for symbolic computation:
```ocaml
type unop = Sin | Cos
type binop = Plus | Mult
type expr =
| Cst of int
| Var of string (* variable name *)
| Unop of unop * expr
| Binop of binop * expr * expr
(* encoding 3x+sin(y) : *)
let exemple = Binop(Plus,Binop(Mult,Cst 3,Var "x"),Unop(Sin,Var "y"))
```
Note that transforming a character string such as `"3x+sin(y)"` into the treeish form above (of type `expr`) could be done automatically, but that's far from obvious. That's a lexical and grammatical analysis, for which dedicated tools exists and are quite handy (cf `ocamllex` et `ocamlyacc`).
We could then perform various operations on expressions, such as simbolic simplification or derivation.
#### Exercise : simplification and formal derivation
- Write a `simpl : expr->expr` function performing as much as possible basic algebraic rules such as `0+x = x`, at any place in the expression.
- Write a `deriv : string->expr->expr` function computing a formal derivation with respect to a given variable name.
## OCaml is far more than that ...
OCaml is a full-fledge general-purpose language, so we've only seen a narrow part of it.
It also provides:
- Exceptions (not only for errors, could be used as a programming style)
- Modules for programming in the large
- Primitives for imperative programming (`for`, `while`, references, mutable arrays, ...)
- Primitives for interacting with the rest of the world (I/O, files, system, networks, ...)
- Object-oriented programming (the "O" of OCaml...)
- Lazy programming
- ...
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment