Questa sezione e' composta da due parti: ESERCIZI HASKELL e ESERCIZI PROGRAMMAZIONE HASKELL
ESERCIZI HASKELL
Esercizio 1
Definire in HASKELL il dato astratto "Tipo" (gli oggetti di questo tipo di dato astratto
rapresentano i tipi dei sistemi a' la Church e a' la Curry per il lambda-calcolo).
Soluzione
Esercizio 2
Supponete di aver definito una funzione Haskell di nome funct.
Dire cosa fa il sistema se si scrive:
:type funct
Dire cosa avrebbe fatto il sistema se prima della definizione di funct uno avesse scritto:
funct :: [Int] -> Bool
Soluzione
Esercizio 3
Definire in HASKELL il tipo di dato astratto Albero binario etichettato con etichette
numeriche.
Soluzione
Esercizio 4
Definire in HASKELL il tipo di dato astratto Albero ternario senza etichette.
Soluzione
Esercizio 5
Definire in Haskell il tipo di dato Albero ternario etichettato.
Discutere brevemente la soluzione proposta.
Soluzione
Esercizio 6
In Haskell ogni termine, prima di venire valutato, viene tipato con un
sistema di assegnamento simile a quello a' la Curry visto nel corso.
I lambda termini tipabili
a' la Curry hanno la proprieta' di essere fortemente normalizzabili.
Questo dovrebbe implicare che ogni "programma" Haskell "termini".
Invece non e' cosi'.
Cosa e' presente in Haskell in piu', rispetto al sistema di assegnamento di tipi
a'la Curry per il lambda calcolo, che fornisce al linguaggio la possibilita'
di avere non terminazione?
Esercizio 7
Supponendo che non sia predefinito, definire in HASKELL il tipo di dato astratto
Lista di Interi. E' possibile definire il tipo di dato Lista di elementi qualsiasi?
Soluzione
Esercizio 8
Cosa ha a che fare il lambda-calcolo tipato a' la Curry con HASKELL? Discutere.
Esercizio 9
La sonda Spirit ha trovato che su Marte esistono i Loffioni.
Un Loffione puo' essere un Gangarone oppure un Sarrapone,
oppure una sequenza finita di Purpuroni.
Un Purpurone e' formato da un Gangarone e un Sarrapone.
In natura il numero di sarraponi e' infinito, invece esistono solo due Gangaroni,
il GangaroneA e il GangaroneB.
Definire in Haskell il tipo di dato astratto Loffione che possa permettere
di scrivere programmi Haskell che simulino esperimenti virtuali sui Loffioni.
Soluzione
Esercizio 10
Le seguente definizioni HASKELL
f n = n:(f(n+1))
nums = f 0
corrispondono, in SCHEME, a
(define (f n) (cons n (f (+ n 1))))
(define nums (f 0))
Che cosa "dovrebbe" fare f? e cosa e' nums?
Perche' in Haskell possiamo utilizzare f e nums (fornire possibilmente
un esempio di uso), mentre in SCHEME non ci possiamo fare in pratica niente?
Perche' comunque, anche se poi non ci possiamo fare un fico secco, SCHEME permette
lo stesso di definire la funzione f senza darci problemi?
Soluzione
Esercizio 11
Presa la seguente definizione di tipo in HASKELL
data Pirimpo = Pompero Int | Zumpo Pirimpo [Int]
scrivere delle definizioni Scheme che permettano di utilizzare
(per qual che e' possibile in Scheme) il tipo di dato Pirimpo.
Soluzione
Esercizio 12
Gli Yunz sono esseri magici che nella contea di Hargaar vengono
usati dagli studenti per passare gli esami.
Uno Yunz puo' venire generato dicendo due numeri qualsiasi mentre
si effettua l'incantesimo del mago Frunz su due Yunz. Il nanetto Yor e'
uno Yunz.
- Definire il tipo di dato Yunz in Haskell.
- Affinche' uno studente possa trarre profitto dalla presenza
di uno Yunz, deve sapere se per generarlo e' mai stato utilizzato il
numero 276 durante l'incantesimo del mago Frunz. Definire un predicato
Haskell che serva a tale scopo.
Soluzione
Esercizio 13
Dire brevemente cosa fa' l'interprete HASKELL quando scriviamo qualcosa
del genere:
funct :: [Int] -> Bool
funct ls = BODY
Esercizio 14
Si considerino le seguenti definizioni Haskell contenure in un file di nome esercizio.hs.
contains x [] = False
contains x (y:ys) = if (x == y) then True else (contains x ys)
data Giuggiolo = Giuggiolo Int
deriving(Show)
data Paperino = Paperosimple Giuggiolo | Paperocomplex [Giuggiolo]
deriving(Show)
cucu :: Paperino -> Giuggiolo -> Bool
cucu (Paperosimple (Giuggiolo n)) (Giuggiolo m) = (n == m)
cucu (Paperocomplex lg) g = (contains g lg)
Se facciamo valutare il file esercizio.hs all'interprete Haskell
riceviamo la seguente segnalazione di errore:
ERROR "esercizio.hs":17 - Instance of Eq Giuggiolo required for definition of cucu
Perche'? Come si risolve il problema?
Soluzione
Esercizio 14
Descrivere in generale il concetto di overloading nei
linguaggi di programmazione. Descrivere poi come questo viene implementato
nelle Type Classes di Haskell. Descrivere inoltre sintassi ed
uso delle Type Classes in Haskell.
Esercizio 15
Monadi in Haskell. Definizione e loro utilizzo.
Esercizio 16
Discutere brevemente dell'uso dell'algoritmo di unificazione nell'interprete Haskell
ed in quello Prolog.
Esercizio 17
Definire la nozione di Pattern matching ed il suo utilizzo in Haskell.
In quale altro linguaggio studiato nel corso viene utilizzata la nozione di pattern matching?
In generale, in cosa differiscono i concetti di Pattern matching e di Unificazione?
Esercizio 18
Cosa restituisce Haskell se forniamo all'interprete la seguente espressione?
:type (>>=)
Quando viene utilizzato in Haskell l'operatore >>= ?
Esercizio 19
Si consideri la seguente funzione Haskell:
myPenultimo (x) | (length x)==1 = x
| (length x)==2 = head x
| otherwise = myPenultimo(tail x)
L'interprete restituisce il seguente errore
Occurs check: cannot construct the infinite type: a0 = [a0]
Expected type: [[a0]]
Actual type: [a0]
In the first argument of `head', namely `x'
In the expression: head x
In an equation for `myPenultimo':
myPenultimo (x)
| (length x) == 1 = x
| (length x) == 2 = head x
| otherwise = myPenultimo (tail x)
Failed, modules loaded: none.
Perche'?
Modificare inoltre la funzione in modo che restituisca il penultimo elemento di una lista
(definendo penultimo di [] come [] e penultimo di una lista con un solo elemento come
l'unico l'elemento).
Soluzione
ESERCIZI PROGRAMMAZIONE HASKELL
Esercizio 1
Scrivere la funzione Haskell "pippo" che corrisponde al lambda termine
\xy.yx
Come si puo' fare in modo che, senza modificare la definizione
della funzione pippo, questa possa accettare solo elementi numerici come
primo input?
Possibile soluzione.
Esercizio 2
Definire una funzione Haskell ricorsiva che prenda in input un
numero naturale n e calcoli la somma di tutti i numeri pari da 0 a n.
(Usare il predicato Haskell che controlla se un numero e' pari )
Esercizio 3
Definire in Haskell la funzione "manoh" che, preso un numero n,
restituisce la somma dei quadrati dei naturali da 0 ad n.
Esercizio 4
Definire in Haskell la funzione "mavah" che, preso un numero n,
restituisce il quadrato della somma dei naturali da 0 ad n.
Esercizio 5
Definire in Haskell la funzione "madaih" che, preso un numero n,
restituisce la somma dei naturali da 0 ad n.
Esercizio 6
Definire in Haskell la funzione "masih" che, preso un numero n,
restituisce il quadrato della somma dei naturali da 0 ad n.
Esercizio 6.5
Supponiamo di aver definito in Scheme una funzione a valori booleani (un predicato) "cucu".
Definire una funzione Scheme che, dati in input due numeri naturali n ed m (n ≤ m), restituisca la somma di tutti i numeri compresi tra n ed m che soddisfano il predicato "cucu".
Esercizio 7
Fornire una funzione Haskell che, preso in input un predicato p
sui naturali restituisca una funzione sui naturali che vale 2 sui
numeri su cui p e' vero e 3 sui numeri su cui
p e' falso.
Esercizio 8
Definire il tipo di dato Simple Type ed
implementare l'algoritmo di unificazione tra Simple Types.
Possibile soluzione.
Esercizio 8.1
Modificare la funzione unifyTypes della soluzione dell'esercizio 8
in modo che l'unificazione di due tipi restituisca una coppia di tipo
(TypeSubst,Bool) tale che il valore booleano sia falso1 se e solo se
i tipi non siano unificabili (in questo caso il valore della sostituzione
deve essere la funzione identita').
Possibile soluzione.
Esercizio 9
Definire una semplice classe ed una sua istanza.
Possibile soluzione.
Esercizio 10
Definire alcuni tipi di dati albero (possibilmente parametrici).
Per esempio, alberi binari etichettati, alberi ternari, alberi con
numeri arbitrari di figli ecc.
Identificare poi alcune operazioni che possano essere utilizzate
su ognuno di tali tipi, per esempio l'altezza.
Definire quindi una classe Tree con tali operazioni, definendo
i tipi precedentemente forniti come istanze di tale classe.
Esercizio 11
Definire la funzione Haskell che implementi l'insertion sort su liste,
avendo come parametro anche la relazione di precedenza.
Possibile soluzione.
Esercizio 12 (un po' complesso)
Definire la funzione Haskell che, dato un grafo e due nodi, restituisca
l'insieme dei cammini (privi di cicli) dal primo al secondo nodo.
Possibile soluzione.
Esercizio 13
Definire in Haskell i tipi di dato Abn ("Albero binario con etichette numeriche")
e Abf ("Albero binario con etichette di tipo Int -> Int").
Definire due semplici elementi di tipo Abn e Abf.
Definire una funzione Haskell
appT :: Abf -> Abn -> Abn
che prenda un albero Abf ed uno Abn con la stessa struttura (non occorre
implementare il controllo che abbiano la stessa struttura) e restituisca
un albero Abn (con la stessa struttura degli alberi in input) in cui le
etichette corrispondano alle applicazioni delle funzioni dell'albero Abf
ai numeri dell'albero Abn.
Nel caso di alberi con struttura differente, e volendo gestire
tale situazione non con la generazione di un messaggio di errore,
si potrebbe utilizzare la monade Maybe.
Ridefinire quindi la funzione appT in modo che abbia il seguente tipo
appT :: Abf -> Abn -> (Maybe Abn)
Possibile soluzione.
Esercizio 14
Definire in Haskell il tipo di dato Lambda-Termine
e realizzare le funzioni fv e bv che calcolano gli insiemi
delle variabili, rispettivamente, libere e legate di un termine.
Per realizzare fv e bv si utilizzi il dato
data Set a = Set [a]
e si supponga di avere a disposizione gli operatori insiemistici "union" e "minus".
Possibile soluzione.
Esercizio 15
Definire in Haskell la lista di tutti i numeri
naturali a partire da 1.
Utilizzarla per definire la funzione che, preso un
predicato sui numeri, restituisca la lista di tutti
i numeri che soddisfano il predicato.
Possibile soluzione.
Esercizio 16
Utilizzare l'esercizio 15 per definire la
lista di tutti i multipli comuni a due numeri
e da questa la funzione che calcoli il minimo comune
multiplo.
Possibile soluzione.
Esercizio 17
Definire il tipo di dato parametrico degli
alberi etichettati in cui ogni nodo puo' avere
un numero arbitrario di figli.
Definire quindi la funzione innesta che, presa in input
un'etichetta label e due alberi, inserisca il secondo
albero come primo figlio di tutti i nodi del primo
albero che hanno label come etichetta.
Rendere il tipo di dato membro della
classe Eq, in modo che sugli alberi la
funzione di uguaglianza corrisponda all'uguaglianza della
loro struttura, senza considerare le etichette.
Possibile soluzione.
Esercizio 18
La seguente funzione Scheme prende in input un predicato su numeri una lista ls contenente
numeri o liste di numeri a qualsiasi livello di annidamento
e restituisce una lista uguale ad ls, ma con 0 al posto dei numeri che
soddisfano il predicato p?
define (guess p? ls)
(if (null? ls)
'()
(cons (cond
( (not (number? (car ls))) (guess p? (car ls)) )
( (p? (car ls)) 0 )
( else (car ls) ) )
(guess p? (cdr ls)) ) ) )
Scrivere una funzione Haskell che faccia la stessa cosa.
Possibile soluzione.
Esercizio 19
Definire in Haskell la lista di tutti i numeri naturali a partire da 1.
Utilizzarla per definire la funzione che, preso un predicato sui numeri, restituisca
la lista di tutti i numeri che soddisfano tale predicato.
Esercizio 20
La sonda Spirit ha trovato che su Marte esistono i Loffioni.
Un Loffione puo' essere generato da un Gangarone oppure da un Sarrapone,
oppure da una sequenza finita di Purpuroni.
Un Purpurone viene generato dall'incontro di un Gangarone e un Sarrapone.
Su Marte ci sono infiniti sarraponi, ma solo due Gangaroni,
il GangaroneA e il GangaroneB.
Definire in Haskell il tipo di dato astratto Loffione che possa permettere
di scrivere programmi Haskell che simulino esperimenti virtuali sui Loffioni.
Scrivere il programma Haskell che, preso un Loffione, restituisca
quante volte il GangaroneA e' intervenuto nella generazione del Loffione dato.
Possibile soluzione.
Esercizio 21
Definire in Haskell il tipo delle funzioni parziali da Interi ad Interi.
Definire la funzione "comp" che, prese due funzioni parziali da interi ad interi,
restituisca la loro composizione.
Definire inoltre la funzione "union" che, prese due funzioni parziali da interi a interi
f e g restituisca, con input x, f(x) se f e' definita su x, g(x) altrimenti.
Possibile soluzione.
Esercizio 22
Supponiamo, per semplicita', che in Pict un valore possa essere un intero,
un canale o una tupla (anche vuota) di valori. Sempre per semplicita',
supponiamo che un pattern Pict possa essere una variabile o una tupla (anche vuota)
di pattern.
Definire in Haskell:
- il tipo di dato Valore di Pict
- il tipo di dato Pattern di Pict
- l'operazione di pattern matching di Pict
Il pattern matching, se ha successo, sappiamo che restituisce un binding.
Si puo' scegliere di definire un binding come una funzione parziale (nello stile
dell'esercizio 1.a), oppure come una lista di coppie, o in qualche altro modo
che preferite. Per semplicita' si puo' supporre che il fallimento di un matching
provochi un errore, ma potete scegliere altre alternative.
Possibile soluzione.
Esercizio 23
Supponiamo di aver bisogno, dati due tipi A e B,
di lavorare con liste contenenti elementi di A e B alternati.
Definire in Haskell tale tipo di dato. Utilizzarlo poi
per definire una funzione "divide" che, presa una lista ls
di Int e Bool alternati, restituisca la coppia formata dalle
due liste ottenute considerando solo gli elementi Int e quelli
Bool in ls.
Il tipo coppia e' denotato in Haskell con (T1,T2). Il costruttore
di coppie e', similmente, (e1,e2). "fst" e' la funzione che restituisce
il primo elemento di una coppia, "snd" il secondo.
Possibile soluzione.
Esercizio 24
Discutere brevemente della Tail Recursion.
Il seguente codice Haskell implementa l'insertion-sort parametrizzato
sul un predicato di precedenza, prec.
insert el [] prec = [el]
insert el (x:xs) prec = if (prec el x)
then (el:(x:xs))
else (x:(insert el xs prec))
insertionsort [] prec = []
insertionsort (x:xs) prec = (insert x (insertionsort xs prec) prec)
Fornire una versione tail-recursive dell'insertion-sort.
(se servissero, si puo' supporre di avere a disposizione le versioni
tail-recursive dell'append e del reverse: appendIt e reverseIt).
Possibile soluzione.
Esercizio 25
Scrivere un programma Haskell che produca la lista di tutti i possibili cammini
tra due nodi forniti in input in un grafo non orientato
(anch'esso fornito in input).
Il grafo e' rappresentato da una lista di archi, rappresentati a loro volta da
coppie di nodi. I nodi sono rappresentati da numeri interi.
Si supponga che se un arco e' rappresentato dalla coppia (n,m), allora il grafo
non contiene anche la coppia (m,n).
archi
Possibile soluzione.
Esercizio 25.1
Modificare il programma dell'esercizio 25 definendo il tipo grafo
come
type Graph = (Set Node, (Node -> (Set Node)))
Esercizio 26
Si fornisca una soluzione dell'esercizio 25 in modo che la chiamata ricorsiva
del programma abbia sempre lo stesso grafo come argomento.
Possibile soluzione.
Esercizio 27
In Haskell, il costruttore di tipo lista e' una monade. Dare un'occhiata
a tale monade nel testo e poi
modificare la soluzione dell'esercizio 25 utilizzando l'operatore >>=
per rendere piu' compatta tale soluzione.
Possibile soluzione.
Esercizio 28
Fornire una soluzione che usi ricorsione di coda per l'esercizio 25.
Possibile soluzione.
Esercizio 29
Definire in Haskell il tipo di dato astratto
Albero Binario con rami infiniti e con nodi con etichette di tipo parametrico a.
Costruire poi l'albero infinito con etichette numeriche
della forma:
1
/ \
/ \
/ \
2 3
/ \ / \
4 5 6 7
/ \ / \ / .......
8 9 10 11 12 ........
.......................
Possibile soluzione.
Esercizio 30
Sia
data InfNBT = Join Int InfNBT InfNBT
il tipo di dato degli alberi binari con rami infiniti ed etichette numeriche.
Si definisca myIT come il seguente elemento di InfNBT
1
/ \
/ \
/ \
2 3
/ \ / \
4 5 6 7
/ \ / \ / .......
8 9 10 11 12 ........
.......................
e poi
si definisca la funzione
levelOrder :: InfNBT → [Int]
che restituisce la lista delle etichette di un albero infinito ordinate livello per livello da sinistra
a destra.
Per esempio, la valutazione di (levelOrder myIT) produrra' la lista infinita
di tutti gli interi a partire da 1.
(Una possibile soluzione potrebbe utilizzare una funzione che, preso un albero ed un numero n,
restituisca la lista di tutti i sottoalberi di livello n)
Si scriva inoltre la piu' breve espressione Haskell il cui valore e' la lista
di tutti gli interi a partire da 1.
Possibile soluzione.
Esercizio 31
Definire un tipo di dato astratto i cui elementi rappresentino termini
PROLOG.
Inserire il tipo di dato nella classe Show in modo che i suoi elementi
vengano visualizzati esattamente come termini PROLOG.
Estendere a tali termini la funzione unify dell'esercizio 8.1
Possibile soluzione.
Esercizio 32
Scrivere un programma Haskell che riconosca o rifiuti stringhe appartenenti al linguaggio
regolare, sull'alfabeto {a,b,c}, associato alla seguente espressione regolare
abc*b + aa
Ricordiamo che il tipo String in Hakell coincide con [Char] e che un carattere si rappresenta tra
apici (es.: 'a').
Possibile soluzione.
Esercizio 33
La seguente funzione Erlang firstList2secondList
prende due liste non vuote di uguale lunghezza e restituisce una funzione che, preso un elemento
della prima lista restituisce l'elemento della seconda lista nella stessa posizione.
In Erlang la funzione predefinita lists:nth(N,L)
restituisce l'N-esimo elemento della lista L. Inoltre la funzione position
prende un elemento ed una lista che lo contiene e restituisce la sua posizione.
Notare inoltre l'uso di "fun" per denotare una funzione anonima.
firstList2secondlist(First,Second) ->
fun(Elem) -> N = position(Elem,First);
lists:nth(N,Second)
end.
Scrivere la stessa funzione in Haskell, senza utilizzare
funzioni ausiliarie come position e lists:nth.
Fornire il tipo principale calcolato da Haskell per la funzione definita.
Come mai in Haskell e' possibile fornire la funzione richiesta
in modo semplice ed elegante, mentre in Erlang cio' risulterebbe
piu' complesso?
Possibile soluzione.
Esercizio 34
Si consideri il seguente codice Haskell incompleto
data ......
treeapply f (Leaf x) = x
treeapply f (Node n t1 t2) = f n (treeapply f t1) (treeapply f t2)
e si completi con la definizione del tipo di dato in modo che questa
risulti la piu' generale possibile.
Cosa calcola la funzione treeapply?
Cosa restituisce l'interprete se chiediamo la cosa seguente?
:type treeapply
Giustificare la risposta.
Possibile soluzione.
Esercizio 35
Supponiamo di aver definito in Haskell un tipo Var che rappresenti un insieme infinito di variabili,
ed un tipo Value che rappresenti un insieme di valori.
Usare la monade Maybe per definire il tipo dei "bindings" che sono associazioni
tra un numero finito di variabili e valori.
Definire una funzione update che, dato un binding B, una variabile var ed un valore val,
restituisca un binding che si comporta come B, ma che su var restituisce val.
(Si puo' assumere che il tipo Var sia nella classe Eq).
Possibile soluzione.
Esercizio 36
Definire in Haskell il tipo di dato "termine Prolog"
ed inserirlo nella classe Show in modo da vedere sullo schermo
i suoi elementi come se fossero reali termini Prolog.
Possibile soluzione.
Esercizio XX
Possibile soluzione.