Programmazione Funzionale:
ML
implementazione del l-calcolo tipato
definizione di nuovi tipi ricorsivi
i valori dei nuovi tipi sono termini, che possono essere visitati con
un meccanismo di pattern matching (versione semplificata
dell’unificazione)
scoping statico (a differenza di LISP)
semantica statica molto potente (inferenza e controllo dei tipi)
• un programma “corretto” per la semantica statica quasi sempre
va bene
gestione della memoria a heap con garbage collector
1
Lo strumento utilizzato seconda
parte del corso
Ocaml (Objective CaML), una estensione, orientata ad
oggetti (e con un frammento imperativo), di uno dei più
importanti linguaggi funzionali (ML)
progettato ed implementato all’INRIA (Francia)
l’implementazione (per tutte le piattaforme importanti) si
può scaricare dal sito
http://caml.inria.fr/
il manuale on line al sito
http://caml.inria.fr/ocaml/htmlman/index.html
2
OCAML
nucleo funzionale puro
funzioni (ricorsive)
tipi e pattern matching
primitive utili: liste
componente imperativo
variabili e assegnamento
primitive utili: arrays
moduli e oggetti
meccanismi per la definizione di tipi e di tipi di
dato astratto
3
Applicazione: macchina astratta
semantica operazionale: implementazione e
strutture a tempo di esecuzuioone
riprenderemo l’interprete di un semplice
linguaggio imperativo (frammento C) visto a
Programmazione I
vedremo come utilizzare le caratteristiche del
linguaggio ad oggetti (meccanismi di astrazione
sui dati)
Seguendo il tutorial
sessione interattiva
l’utente scrive frasi CAML che iniziano con
prompt #
a seguito del prompt, il sistema compila le frasi,
le esegue e stampa il risultato della valutazione
per ogni frase il risultato e’ un valore ed il tipo
(type inference)
Espressioni pure
Objective Caml version 2.00+3/Mac1.0a1
# 25;;
- : int = 25
# true;;
- : bool = true
# 23 * 17;;
- : int = 391
# true & false;;
- : bool = false
# 23 * true;;
This expression has type bool but is here used with type int
# if 2 = 3 then 23 * 17 else 15;;
- : int = 15
# if 2 = 3 then 23 else true;;
This expression has type bool but is here used with type int
6
Funzioni
# function x -> x + 1;;
- : int -> int = <fun>
# (function x -> x + 1) 3;;
- : int = 4
# (function x -> x + 1) true;;
This expression has type bool but is here used with
type int
anche i parametri delle funzioni non richiedono
annotazioni di tipo (il tipo e’ inferito dal loro uso nel
corpo)
il valore di una funzione e’ <fun>
7
Funzioni
#
#
#
#
#
#
-
function x -> x;;
: 'a -> 'a = <fun>
function x -> function y -> x y;;
: ('a -> 'b) -> 'a -> 'b = <fun>
(function x -> x) 2;;
: int = 2
(function x -> x) (function x -> x + 1);;
: int -> int = <fun>
function (x, y) -> x + y;;
: int * int -> int = <fun>
(function (x, y) -> x + y) (2, 33);;
: int = 35
‘a e’ una variabile di tipo, puo’ assumere qualsiasi tipo (polimorfismo)
le funzioni sono valori, possono essere passate come parametri
(funzioni di ordine superiore)
8
Il costrutto let \ let in
permette di definire un nuovo identificatore a con
associato un valore
valori: simple expressions o funzioni
Let binding
# let x
val x :
# x;;
- : int
# let y
- : int
# y;;
Unbound
•
= 3;;
int = 3
= 3
= 5 in x + y;;
= 8
value y
(definizione globale/locale)
10
Let binding
# let f = function x -> x + 1;;
val f : int -> int = <fun>
# f 3;;
- : int = 4
# let f x = x + 1;;
val f : int -> int = <fun>
# f 3;;
- : int = 4
# let fact x = if x = 0 then 1 else x * fact(x - 1) ;;
Unbound value fact
11
Let rec binding
# let rec fact x = if x = 0 then 1 else x * fact(x - 1)
;;
val fact : int -> int = <fun>
# fact (x + 1);;
- : int = 24
12
Type
per definire nuove strutture dati vengono
forniti records e variants (unione)
entrambi si definiscono con una
dichiarazione di tipo type
Record
# type ratio={num:int; denum:int};;
type ratio={num:int; denum:int}
# let x={num=1;denum=3);;
val x: ratio={num=1;denum=3;}
# x.num;;
- int =1
• un record per memorizzare numeri razionali
• creare e selezionare i campi
14
Unione disgiunta, variant
• enumera tutti i casi che possono assumere i
valori di quel tipo
• ogni caso e’ identificato da un nome
(costruttore) che serve per costruire valori
di quel tipo e per riconscerlo per patternmatching
• utile per definire tipi di dato ricorsivi
15
# type number = | Int of int
type number=
| Int of int
| Float of float
| Error
| Float of float
# let add_num n1 n2=
match (n1,n2) with
(Int i1,Int i2)--->.......
(Int i1,Float i2)--->
(Float i1,Float i2)--->
(Float i1,Int i2)--->
(Error,-)---------->Error
(-,Error)---------->Error
val add_num: number->number->number=<fun>
# Int 3;;
- : number= Int 3;
| Error;;
Espressioni: sintassi astratta
# type ide = string;;
type ide = string
# type expr = | Den of ide | Val of ide | Fun of ide * expr
| Plus of expr * expr | Apply of expr * expr;;
type expr =
| Den of ide
| Val of ide
| Fun of ide * expr
| Plus of expr * expr
| Apply of expr * expr
E ::= I | val(I) | lambda(I, E1) | plus(E1, E2) | apply(E1, E2)
# Apply(Fun("x",Plus(Den "x", Den "x")), Val "y");;
- : expr = Apply (Fun ("x", Plus (Den "x", Den "x")), Val "y")
17
L’ambiente
# type eval = Int of int | Bool of bool | Efun of expr
| Unbound;;
type eval = | Int of int | Bool of bool | Efun of expr |
Unbound
# type env = ide -> eval;;
type env = ide -> eval
# let bind (rho, i, d) =
function x -> if x = i then d else rho x;;
val bind : (ide -> eval) * ide * eval -> (ide -> eval) = <fun>
- env = IDE eval
- eval = [ int + bool + fun ]
18
Comandi: sintassi astratta
# type com = Assign of ide * expr | Ifthenelse of expr *
com list * com list | While of expr * com list;;
type com =
| Assign of ide * expr
| Ifthenelse of expr * com list * com list
| While of expr * com list
C ::= ifthenelse(E, C1, C2) | while(E, C1) | assign(I, E) | cseq(C1, C2)
# While(Den "x", [Assign("y", Plus(Val "y", Val "x"))]);;
- : com =
While (Den "x", [Assign ("y", Plus (Val "y", Val "x"))])
19
Tipi e pattern matching
type expr =
| Den of ide
| Fun of ide * expr
| Plus of expr * expr
| Apply of expr * expr
type eval = | Int of int | Bool of bool | Efun of expr | Unbound
type env = ide -> eval
E(I, ) = (I)
E(plus(E1, E2), )= E(E1, ) + E(E2, )
E(lambda(I, E1), )= lambda(I, E1)
E(apply(E1, E2), ) = applyfun (E(E1, ), E(E2, ), )
applyfun(lambda(I, E1), d, ) = E(E1) [ / I d ]
# let
|
|
|
|
rec sem (e, rho) = match e with
Den i -> rho i
Plus(e1, e2) -> plus(sem (e1, rho), sem (e2, rho))
Fun(i, e) -> Efun(Fun(i, e))
Apply(e1, e2) -> match sem(e1, rho) with
| Efun(Fun(i, e)) -> sem(e, bind(rho, i, sem(e2, rho))
| _ -> failwith("wrong application");;
val sem : expr * env -> eval = <fun>
20
Un tipo primitivo utile: le liste
Si costruiscono enumerando esplicitamente
gli elementi, racchiusi tra []
Oppure a partire dalla lista vuota [] tramite
l’operatore di cons
Operazioni primitive standard
Liste
# let l1 = [1; 2; 1];;
val l1 : int list = [1; 2; 1]
# let l2 = 3 :: l1;;
val l2 : int list = [3; 1; 2; 1]
# let l3 = l1 @ l2;;
val l3 : int list = [1; 2; 1; 3; 1; 2; 1]
# List.hd l3;;
- : int = 1
# List.tl l3;;
- : int list = [2; 1; 3; 1; 2; 1]
# List.length l3;;
- : int = 7
22
Caratteristiche Imperative
variabili e assegnamento
arrays (mutabili)
comandi iterativi, while, for
Variabili e frammento
imperativo
# let x = ref(3);;
val x : int ref = {contents=3}
# !x;;
- : int = 3
# x := 25;;
- : unit = ()
# !x;;
- : int = 25
# x := !x + 2; !x;;
- : int = 27
references sono mutable indirection cells, il cui
valore puo’ essere modificato da un assegnamento
24
Un tipo primitivo mutabile:
l’array
# let a = [| 1; 2; 3 |];;
val a : int array = [|1; 2; 3|]
# let b = Array.make 12 1;;
val b : int array = [|1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 1|]
# Array.length b;;
- : int = 12
# Array.get a 0;;
- : int = 1
# Array.get b 12;;
Uncaught exception: Invalid_argument("Array.get")
# Array.set b 3 131;;
- : unit = ()
# b;;
- : int array = [|1; 1; 1; 131; 1; 1; 1; 1; 1; 1; 1; 1|]
25
Programmi Stand-alone
Il programma da eseguire deve essere
memorizzato in un file .ml
Compilare ed eseguire
26
Esercizio 1
Funziona per ordinare una lista polimorfa
Le liste sono un tipo di dato primitivo immutabile
(in stile funzionale), a differenza degli array
La funzione sort ha tipo
'a list -> 'a list
• Gli operatori di confronto (=,<=,….) sono
polimorfi, si applicano ad ogni tipo
27
Cosa vogliamo?
# let l1 = [6; 2; 5; 3];;
val l1 : int list = [6; 2; 5;3]
# sort l1 ;;
- : int list = [2; 3; 5;6]
# let l2 = [3.14; 2.17];;
val l1 : int list = [3.14; 2.17]
# sort l2 ;;
- : float list = [2.17; 3.14]
• La lista passata come input non e’ modificata, la funzione
restituisce una nuova lista, che contiene gli elementi in
ordine crescente
Insertion sort
Realizzare sort in modo ricorsivo
caso base: []
caso ricorsivo cons: head:: tail
• Pattern matching
• Insertion sort (funzione ausiliaria che
inserisce un elemento in una lista ordinata)
Funzioni di ordine superiore
in puro stile funzionale le funzioni sono
valori, possono essere passate come
parametri ad altre funzioni
• E’ utile per realizzare operazioni su
strutture dati
List.map
# let l1 = [6; 2; 5; 3];;
val l1 : int list = [6; 2; 5;3]
# List.map (function n -> n+1) l1 ;;
- : int list = [7; 3; 6;4]
• E’ un funzionale standard, applica una funzione ad ogni
elemento di una lista
• E’ fornito dalla libreria, ma non c’e’ niente di magico
• Definiamolo, come una funzione ricorsiva di ordine
superiore
Alberi binari
Definire un tipo unione ‘a btree che specifica
alberi binari polimorfi
Definizione ricorsiva:
caso base: vuoto
caso ricorsivo: un nodo che contiene un valore
di tipo ‘a, e due sottoalberi ‘a btree sx e dx
32
Btree
# type ‘a btree= Empty| Node of ‘a * ‘a btree * ‘a btree;;
type ‘a btree= Empty| Node of ‘a * ‘a btree * ‘a btree
# Empty;;
- : ‘a btree = Empty
# Node(1,Empty,Empty);;
-: int btree = Node(1,Empty,Empty)
# Node;;
the constructor node expects 3 arguments, but is here applied to zero
arguments
Funzioni
Scrivere una funzione che, dato un ‘a btree,
calcola il numero di nodi
Ordered Binary Btree: ‘a btree polimorfi ma
ordinati, il sottoalbero sx ha un valore strettamente
minore, e quello dx un valore strettamente
maggiore di quello del nodo
Scrivere due funzioni che, dato un ordered ‘a
btree ed un valore x di tipo ‘a , fanno la
ricerca o l’inserimento di x
Osservazione
Le funzioni member e insert come
parametro un btree
Assumiamo che sia di tipo Ordered
Non possiamo definire un tipo di dato
Ordered per costruzione
Che cosa serve? Un meccanismo per
definire un tipo di dato astratto
Tipi di dato astratto
Dati + operazioni : insieme
In questo modo possiamo definire proprieta’
dei dati (tipo l’ordine per costruzione)
Meccanismi di astrazione, incapsulamento
dell’informazione, in modo da garantire che
le proprieta’ non siano violate dal codice
che usa il tipo di dato