360 likes | 469 Views
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)
E N D
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 Programmazione Funzionale: ML 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
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>
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 = 3;; val x : int = 3 # x;; - : int = 3 # let y = 5 in x + y;; - : int = 8 # y;; Unbound value y • (definizione globale/locale)
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
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
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 pattern-matching utile per definire tipi di dato ricorsivi 15
# type number = | Int of int | Float of float | Error;; type number= | Int of int | Float of float | Error # 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;
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 ]
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"))])
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) [r / 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
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
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|]
Programmi Stand-alone • Il programma da eseguire deve essere memorizzato in un file .ml • Compilare ed eseguire
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 btreepolimorfi 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 btreeed 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