## Introduction to ML and functional programming

### Taxonomy of programming languages

• Imperative: C, Pascal, Fortran, ...
• Object-oriented: Small Talk, C++, Java, ...
• Functional: Lisp, ML, Haskell, Postscript, ...
• Logical: Prolog, lambdaProlog, ...
Most langauges blend various features. Consider the spectrum of Imperative versus Functional:
```   Assembly Languages           C/Pascal              ML   Pure Functions

<---------------------------------------------------------------------->
Only commands                                         Only expressions

LOAD R1 5                    R3 :=   5 + 7 ;
SUM R1 R2 R3                           ^
|
expression
```

### Characteristics of Functional Programming

• Automatic garbage collection
• Static (i.e. lexical) scoping (Except LISP)
• Fixed evaluation strategy; usually call-by-value (Except Askell which has call-by-name)
• Higher Order
• Modern functional languages, like ML, are strongly typed, polymorphic, and allow side-effects and exceptions
Interpretation is based on a read-eval-print loop.

### Declarations and Expressions in ML

The general form of a declaration is
```   - val < Ide > = < Expression >
```
Examples of expressions are:
• Arithmetic expressions. Examples:
```  5, 5+6, 5*x
```
• Boolean expressions. Examples:
```   true, false, x>5, x>5 orelse y>7
```
• Let expressions. Example:
```   let val x = 7
in 5*x
end
```
They can also be nested. Example:
```   let val x = 7
in let val y = 5
in y*x
end
end
```
The type of a let expression is the same as the type of the body.

• if-then-else expressions. Example:
```   if x>0 then x else 0-4
```
The type of an if-then-else is the same as the type of the "then" and the "else" branches. These types have to be the same.

### Recursive definitions in ML

Recursion is the basic method of programming in ML. Functions can be defined recursively in the usual way, i.e. by making, in the body of the definition, one or more calls to the function we are defining. Examples
• The factorial function can be defined as
```   fun fact n = if n = 0
then 1
else n * fact (n-1);
```
• The Fibonacci function can be defined as
```   fun fib n = if n = 0
then 0
else if n = 1
then 1
else fib(n-2) + fib(n-1);
```
Function calls are expressions, for instance we can write
```   fact(fib 6)) + 2
```
which will evaluate to the number 10.

### Definition of functions by pattern-matching

ML allows to define function by pattern matching, which, intuitively, corresponds to to defining a function "by cases". A pattern is an expression which can contain variables (each occurring only once), constants, tuple constructors (i.e. parentheses and commas), and data type constructors (i.e. operations like "cons" on lists, see below). The general form of a definition of a function f by pattern matching is:
```   fun f pattern_1 = expression_1
| f pattern_2 = expression_2
...
| f pattern_n = expression_n;
```
where each expression_i can contain the variables in the pattern_i

When we make a function call

```   f e;
```
the argument expression e is evaluated, and the result v is confronted with the various patterns, from the first (pattern_1) to the last (pattern_n). The first pattern pattern_i which "matches" v (i.e. which can be made equal to v by assigning a suitable value to the variables of the pattern) is selected, and the result of the call will be given by the result of the corresponding expression expression_i, with the value of the pattern variables extablished by the matching. Examples Let us see how we could define the above functions of factorial and Fibonacci by pattern matching:
• The factorial function:
```   fun fact 0 = 1
| fact n = n * fact (n-1);
```
• The Fibonacci function:
```   fun fib 0 = 0
| fib 1 = 1
| fib n = fib(n-2) + fib(n-1);
```

### Lists in ML

ML has lists as a predefined type. The syntax of the list constructors is:
• nil, for the empty list, and
• ::, for the cons operator.
We can also use the notation [a1,a2,...,an] to represent a list whose elements are a1, a2, ..., an. For instance, [] stands for nil and [1,2,3] stands for 1::2::3::nil.

Some of the list destructors are also predefined in ML. In particular

• hd, which gives the head (first element) of a list, and
• tl, which gives the tail of the list, namely the list obtained by removing the first element.
There are also other functions on list which are predefined in ML. For instance, "append" is predefined and represented in infix notation by the symbol "@".

#### Examples of functions on lists

• Function which tells whether a list is empty or not
```   fun isnil(nil) = true
| isnil(_)   = false;
```
• Function append
```   fun append(l,k) = if isnil(l)
then k
else hd(l)::append(tl(l),k);
```
We could also have used pattern-matching:
```   fun append(nil,k)  = k
| append(x::l,k) = x::append(l,k);
```
• Function which reverses the order of the elements of a list
```   fun reverse(l) = if isnil(l)
then nil
else append(reverse(tl(l)),(hd(l)::nil));
```
• A better (faster) version of reverse, using an auxiliary function rev, defined by tail-recursion
```
fun rev(l,acc) = if isnil(l)
then acc
else rev(tl(l),(hd(l)::acc));

fun reverse1(l) = rev(l,nil);
```
Exercise: rewrite the two definitions of reverse by using pattern-matching.
• Function which merges two ordered lists into an ordered list
```   fun merge(l,k) = if isnil(l)
then k
else if isnil(k)
then l
else let val x = hd(l) and y = hd(k)
in if x <= y
then (x :: merge(tl(l),k))
else (y :: merge(l,tl(k)))
end;
```
The pattern-matching version of the definition would be
```
fun merge(nil,k)     = k
| merge(l,nil)     = l
| merge(x::l,y::k) = if x <= y
then (x :: merge(l,y::k))
else (y :: merge(x::l,k))
```

• Function quicksort, using an auxiliary function split:
```   fun split(x,nil)    = (nil, nil)
| split(x,(h::t)) = let val (k,m) = split(x,t)
in if h <= x
then ((h::k),m)
else (k,(h::m))
end;

fun quicksort(nil)  = nil
| quicksort(h::t) = let val (low,high) = split(h,t)
in append(quicksort(low), (h::quicksort(high)))
end;
```
Note that in the function split we have used a declaration
```   val (k,m) = split(x,t)
```
In general, the meaning of a declaration of the form
```   val (x1,x2,...,xn) = expression
```
is the following:
Evaluate the expression. The result should be a n-tupla of values, (v1,v2,...,vn) (otherwise we get an error). Then associate the values v1, v2, ..., vn to x1, x2, ..., xn, respectively.
The declaration of a tuple of variables, like the above, is a special case of declaration using pattern-matching. The general format is:
```   val pattern = expression
```
The meaning is the following:
Evaluate the expression. The result v should be of the same type as the pattern, otherwise we get an error. Then match v against the pattern, and assign to the variables of the pattern the values that make the pattern equal to v.

### Tuples in ML

Tuples in ML correspond to records. The generic type for a n_tuple is T1 * T2 * ... * Tn, where T1, T2, ..., Tn are types. "*" represents the cartesian product.

For instance the type T1 * T2 stand for all the set of pairs of the form (v1,v2), where v1 is a value of type T1, and v2 is a value of type T2.

In the example above, split is a function which takes an argument of type T * (T list) and returns a result of type (T list) * (T list). "T list" is the type of the lists whose elements have type T. The compact notation for this is:

```   split : T * (T list) -> (T list) * (T list)
```
where the symbol ":" stands for "has type" and "->" represents the functional arrow.

The precise syntax of types, the type inference mechanism of ML etc. will be explained in future lectures.

### Memory management in ML

We do not have to worry about memory management in ML, because the garbage collection takes care of deallocating structures which are not used anymore.

Consider for instance the expression

```   append(append([1,2],[3,4]),[5,6])
```
the evaluation of this expression will create an intermediate structure L as the result of the innermost append, namely the list [1,2,3,4]. L is not used in the final result [1,2,3,4,5,6], because in the final result the elements 1, 2, 3, 4 are created "ex-novo". We do not have any way to refer to L either, so it cannot be used anywhere else in the program. Hence, after the evaluation of the expression, L becomes "garbage". Anyway, we do not have to worry about it, because the automatic garbage collector will clean up the memory at some time and recollect L.

### Lack of side effects in ML

In general we do not have side-effects in ML (unless we use the special type "ref"), i.e. we cannot change the value of a name by executing an expression. As a consequence, we do not have to worry about "sharing" or "aliasing".

Consider for example the following expression

```   let val l = [1,2,3]
in let val k = [4]
in let m = append(l,k)
in m
end
end
end;
```
Are m and k sharing a portion of list? There is no way to tell, since there is no way to change their value. So, whether or not they are shared is an implementation issue, not a language issue.