- Imperative: C, Pascal, Fortran, ...
- Object-oriented: Small Talk, C++, Java, ...
- Functional: Lisp, ML, Haskell, Postscript, ...
- Logical: Prolog, lambdaProlog, ...

Assembly Languages C/Pascal ML Pure Functions <----------------------------------------------------------------------> Only commands Only expressions LOAD R1 5 R3 := 5 + 7 ; LOAD R2 7 ---------- SUM R1 R2 R3 ^ | expression

- 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

- 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.

- 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);

fact(fib 6)) + 2which will evaluate to the number

fun f pattern_1 = expression_1 | f pattern_2 = expression_2 ... | f pattern_n = expression_n;where each

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 (

- 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);

`nil`, for the empty list, and`::`, for the cons operator.

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.

- 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 befun 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;

val (k,m) = split(x,t)In general, the meaning of a declaration of the form

val (x1,x2,...,xn) = expressionis the following:

Evaluate the expression. The result should be a n-tupla of values,The declaration of a tuple of variables, like the above, is a special case of declaration using pattern-matching. The general format is:(v1,v2,...,vn)(otherwise we get an error). Then associate the valuesv1, v2, ..., vntox1, x2, ..., xn, respectively.

val pattern = expressionThe 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.

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.

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.

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.