Premessa

Ciao! Se è la prima volta che capiti su questo sito, ti consiglio di consultare la pagina principale di questo cosiddetto Giardino Digitale per scoprire meglio cos’è e come navigarlo.

Lo stato di questa nota è al momento: 🔴 Bozza.


Ora proviamo a esplorare un po’ la sintassi di base di Haskell.

Sintassi: aritmetica di base in Haskell

In Haskell, è valido usare i soliti operatori aritmetici +, -, *, / e parentesi per dare precedenze alle operazioni:

ghci> 2 + 15
17
ghci> 49 * 100
4900
ghci> 1892 - 1472
420
ghci> 5 / 2
2.5
ghci> (50 * 100) - 4999
1
ghci> 50 * 100 - 4999
1
ghci> 50 * (100 - 4999)
-244950

Se come secondo operando c’è un numero negativo, bisogna racchiuderlo tra parentesi, altrimenti GHC non capisce cosa vogliamo fare. Per esempio, la moltiplicazione tra 5 e -3 si scrive 5 * (-3) e non 5 * -3 perché in questo caso GHC interpreta quel - non come segno negativo del 3 ma come una sottrazione.

Anche l’algebra booleana è, come al solito, utilizzabile tramite i valori di verità True e False, manipolabili attraverso le operazioni &&, || e not:

ghci> True && False
False
ghci> True && True
True
ghci> False || True
True
ghci> not False
True
ghci> not (True && True)
False

Il test per l’uguaglianza tra due oggetti è fatto per mezzo degli operatori == e /=. Valgono anche gli operatori di confronto <, <=, > e >=:

ghci> 5 == 5
True
ghci> 1 == 0
False
ghci> 5 /= 5
False
ghci> 5 /= 4
True
ghci> "hello" == "hello"
True

Attenzione: errori in Haskell

Se proviamo a eseguire del codice Haskell non corretto dal punto di vista sintattico, GHC terminerà il programma con un errore. Per esempio, provando a sommare il numero 5 e la stringa "llama", otteniamo:

ghci> 5 + "llama"
<interactive>:4:1: error: [GHC-39999]
 No instance for Num String' arising from the literal 5'
 In the first argument of(+)', namely 5'
In the expression: 5 + "llama"
In an equation for it': it = 5 + "llama"

Ciò accade perché la stringa "llama" non è un numero e GHC non sa come sommargli 5. La stessa cosa accadrebbe anche se al posto di "llama" mettessimo "four" o "4" perché anche queste sono stringhe e non numeri (fai caso alle virgolette " attorno al numero!).

Consiglio: codici identificativi degli errori in Haskell

Ogni errore in Haskell ha un codice identificativo della forma X-Y, dove X è il programma in cui è avvenuto l’errore (es. GHC) e Y indica il numero dell’errore.

Per esempio, l’errore di prima è rappresentato dal codice GHC-39999. Puoi approfondire il significato di ogni codice nell’Haskell Error Index.

Sintassi: chiamata di funzioni in Haskell e notazioni

In Haskell, ogni cosa è una funzione. Per esempio, l’operatore * è una funzione che prende due numeri e li moltiplica. Questo operatore usa la notazione infissa, in cui l’operatore è posto in mezzo tra i due parametri (es. per fare la moltiplicazione tra 5 e 7 scriviamo 5 * 7 con il * nel mezzo).

La maggior parte delle funzioni, però, usa la notazione prefissa, cioè quella in cui l’operatore è posto prima dei parametri che richiede. Per esempio, se vogliamo usare la funzione succ che restituisce il successore di un numero intero per trovare il successore di 6, dobbiamo scrivere succ 6:

ghci> succ 6
7

Per le funzioni con più parametri, come la funzione min che prende due parametri e restituisce quello minore, agiamo allo stesso modo: per calcolare per esempio il minimo tra 2 e 3 scriviamo min 2 3:

ghci> min 2 3 
2
ghci> max 100 101  
101  

In Haskell si usa la notazione prefissa per rendere la notazione il più simile possibile a quanto avviene nel -calcolo, nelle cui applicazioni viene posta prima la funzione e poi gli argomenti da applicare a quella funzione.

L’applicazione di funzioni ha la più alta precedenza tra tutti gli operatori. Per esempio, non c’è differenza tra lo scrivere

ghci> succ 9 + max 5 4 + 1  
16 

e

ghci> (succ 9) + (max 5 4) + 1  
16  

È possibile usare le funzioni prefisse con la notazione infissa se le circondiamo con dei backtick (Shift+' per scriverlo). Per esempio, min 1 3 può essere scritto anche come

ghci> 1 `min` 3
1

1 - Prima funzione in Haskell

In the previous section we got a basic feel for calling functions. Now let’s try making our own! Open up your favorite text editor and punch in this function that takes a number and multiplies it by two.

doubleMe x = x + x

Functions are defined in a similar way that they are called. The function name is followed by parameters separated by spaces. But when defining functions, there’s a = and after that we define what the function does. Save this as baby.hs or something. Now navigate to where it’s saved and run ghci from there. Once inside GHCi, do :l baby. Now that our script is loaded, we can play with the function that we defined.

ghci> :l baby
[1 of 1] Compiling Main ( baby.hs, interpreted )
Ok, one module loaded.
ghci> doubleMe 9
18
ghci> doubleMe 8.3
16.6

Because + works on integers as well as on floating-point numbers (anything that can be considered a number, really), our function also works on any number. Let’s make a function that takes two numbers and multiplies each by two and then adds them together.

doubleUs x y = x2 + y2

Simple. We could have also defined it as doubleUs x y = x + x + y + y. Testing it out produces pretty predictable results (remember to append this function to the baby.hs file, save it and then do :l baby inside GHCi).

ghci> doubleUs 4 9
26
ghci> doubleUs 2.3 34.2
73.0
ghci> doubleUs 28 88 + doubleMe 123
478

As expected, you can call your own functions from other functions that you made. With that in mind, we could redefine doubleUs like this:

doubleUs x y = doubleMe x + doubleMe y

This is a very simple example of a common pattern you will see throughout Haskell. Making basic functions that are obviously correct and then combining them into more complex functions. This way you also avoid repetition. What if some mathematicians figured out that 2 is actually 3 and you had to change your program? You could just redefine doubleMe to be x + x + x and since doubleUs calls doubleMe, it would automatically work in this strange new world where 2 is 3.

Functions in Haskell don’t have to be in any particular order, so it doesn’t matter if you define doubleMe first and then doubleUs or if you do it the other way around.

Now we’re going to make a function that multiplies a number by 2 but only if that number is smaller than or equal to 100 because numbers bigger than 100 are big enough as it is!

doubleSmallNumber x = if x <= 100
then x*2
else x

this is you

Right here we introduced Haskell’s if statement. You’re probably familiar with if statements from other languages. The difference between Haskell’s if statement and if statements in imperative languages is that the else part is mandatory in Haskell. In imperative languages you can just skip a couple of steps if the condition isn’t satisfied but in Haskell every expression and function must return something. We could have also written that if statement in one line but I find this way more readable. Another thing about the if statement in Haskell is that it is an expression. An expression is basically a piece of code that returns a value. 5 is an expression because it returns 5, 4 + 8 is an expression, x + y is an expression because it returns the sum of x and y. Because the else is mandatory, an if statement will always return something and that’s why it’s an expression. If we wanted to add one to every number that’s produced in our previous function, we could have written its body like this.

doubleSmallNumber’ x = (if x > 100 then x else x*2) + 1

Had we omitted the parentheses, it would have added one only if x wasn’t greater than 100. Note the ’ at the end of the function name. That apostrophe doesn’t have any special meaning in Haskell’s syntax. It’s a valid character to use in a function name. We usually use ’ to either denote a strict version of a function (one that isn’t lazy) or a slightly modified version of a function or a variable. Because ’ is a valid character in functions, we can make a function like this.

conanO’Brien = “It’s a-me, Conan O’Brien!”

There are two noteworthy things here. The first is that in the function name we didn’t capitalize Conan’s name. That’s because functions can’t begin with uppercase letters. We’ll see why a bit later. The second thing is that this function doesn’t take any parameters. When a function doesn’t take any parameters, we usually say it’s a definition (or a name). Because we can’t change what names (and functions) mean once we’ve defined them, conanO’Brien and the string “It’s a-me, Conan O’Brien!” can be used interchangeably.

2 - Liste in Haskell

Una delle strutture dati più usate sono le liste e in Haskell si possono implementare in diversi modi.

Sintassi: lista in Haskell

In Haskell, una lista è una struttura dati omogenea (cioè formata dallo stesso tipo di dati). Una lista è rappresentata da due parentesi quadre [] che racchiudono gli elementi della lista separati da virgole, per esempio:

ghci> lostNumbers = [4,8,15,16,23,42]  
ghci> lostNumbers  
[4,8,15,16,23,42]  

Le liste vuote sono rappresentate da parentesi quadre [] vuote.

Sintassi: lista annidata in Haskell

In Haskell è possibile creare anche una lista annidata, cioè una lista che contiene altre liste:

ghci> b = [[1,2,3,4],[5,3,3,3],[1,2,2,3,4],[1,2,3]]  
ghci> b  
[[1,2,3,4],[5,3,3,3],[1,2,2,3,4],[1,2,3]]  
ghci> b ++ [[1,1,1,1]]  
[[1,2,3,4],[5,3,3,3],[1,2,2,3,4],[1,2,3],[1,1,1,1]]  
ghci> [6,6,6]:b  
[[6,6,6],[1,2,3,4],[5,3,3,3],[1,2,2,3,4],[1,2,3]]  
ghci> b !! 2  
[1,2,2,3,4]  

Osservazione: vincoli di tipo nelle liste annidate

Le liste all’interno di una lista annidata possono avere lunghezze diverse, ma non possono essere di tipi diversi: così come non puoi avere una lista che contiene sia caratteri che numeri, non puoi avere una lista che contiene sia liste di caratteri che liste di numeri.

Esattamente come per il C, anche Haskell considera le stringhe come liste di caratteri.

Sintassi: stringa in Haskell

In Haskell una stringa è una lista di caratteri. Ogni stringa può essere rappresentata con la solita notazione con le parentesi quadre [] che racchiudono i caratteri della stringa separati da virgole, oppure circondando la stringa con delle virgolette "":

"hello" equivale a ["h", "e", "l", "l", "o"]

2.1 - Operazioni con liste

Sintassi: concatenazione di liste

In Haskell è possibile concatenare due liste (cioè unire le due liste giustapponendo agli elementi della prima quella della seconda) attraverso l’operatore ++, detto operatore di concatenazione o, in inglese, concat operator:

ghci> [1,2,3,4] ++ [5,6,7] == [1,2,3,4,5,6,7]
True

Ciò vale anche per le stringhe dato che anch’esse sono considerate liste:

ghci> "Hello" ++ " " ++ "world!" == "Hello world!"
True

Attenzione: concatenazione di liste troppo grandi

Quando concateniamo due liste, anche se si concatena una lista singoletto (cioè con un singolo elemento) a una lista, per esempio [1,2,3] ++ [4], internamente Haskell deve attraversare l’intera lista a sinistra del ++ per trovarne l’ultimo elemento e concatenarle la seconda lista.

Ciò non è un problema con liste anche con qualche centinaio di elementi, però se si usa l’operatore di concatenazione ++ su liste troppo grandi (es. da centinaia di milioni di elementi) Haskell ci metterà un pochino a scorrerla tutta.

Sintassi: anteposizione di un elemento a una lista

In Haskell è possibile anteporre un elemento a una lista (cioè metterlo all’inizio della lista) attraverso l’operatore :, detto operatore di anteposizione o, in inglese, cons operator:

ghci> 1 : [2, 3, 4] == [1, 2, 3, 4]
True

Ovviamente anche questo operatore vale anche per le stringhe, essendo anch’esse liste:

ghci> "A" : " small cat" == "A small cat"
True

Sintassi: accesso agli elementi di una lista con !!

In Haskell è possibile accedere a un elemento di una lista in base alla sua posizione (indice) attraverso l’operatore !!. Gli indici iniziano da 0:

ghci> "Steve Buscemi" !! 6  
'B'  
ghci> [9.4,33.2,96.2,11.2,23.25] !! 1  
33.2  

Questo operatore funziona anche con le stringhe, essendo anch’esse liste di caratteri. Se si tenta di accedere a un indice fuori dai limiti della lista, Haskell genererà un errore.

Sintassi: confronto di liste

In Haskell è possibile confrontare usando gli operatori <, <=, > e >=, a condizione che gli elementi contenuti siano confrontabili. Le liste vengono confrontate in ordine lessicografico: prima si confrontano gli elementi in testa (il primo elemento), se sono uguali si confrontano i secondi elementi, e così via:

ghci> [3,2,1] > [2,1,0]
True
ghci> [3,2,1] > [2,10,100]
True
ghci> [3,4,2] > [3,4]
True
ghci> [3,4,2] > [2,4]
True
ghci> [3,4,2] == [3,4,2]
True

Sintassi: funzione head

La funzione head prende una lista e restituisce il suo primo elemento:

ghci> head [5,4,3,2,1]
5

Sintassi: funzione tail

La funzione tail prende una lista e restituisce la sua coda, cioè tutto ciò che rimane dopo aver rimosso il primo elemento:

ghci> tail [5,4,3,2,1]
[4,3,2,1]

Sintassi: funzione last

La funzione last prende una lista e restituisce il suo ultimo elemento:

ghci> last [5,4,3,2,1]
1

Sintassi: funzione init

La funzione init prende una lista e restituisce tutto ciò che rimane dopo aver rimosso l’ultimo elemento:

ghci> init [5,4,3,2,1]
[5,4,3,2]

Attenzione: funzioni su liste vuote

Le funzioni head, tail, last e init generano un errore se applicate a liste vuote. Se tenti di ottenere l’elemento in testa di una lista vuota, Haskell genererà un’eccezione:

ghci> head []
*** Exception: Prelude.head: empty list

Sintassi: funzione length

La funzione length prende una lista e restituisce la sua lunghezza:

ghci> length [5,4,3,2,1]
5

Sintassi: funzione null

La funzione null verifica se una lista è vuota, restituendo True se la lista è vuota o False altrimenti:

ghci> null [1,2,3]
False
ghci> null []
True

Sintassi: funzione reverse

La funzione reverse inverte l’ordine degli elementi di una lista:

ghci> reverse [5,4,3,2,1]
[1,2,3,4,5]

Sintassi: funzione take

La funzione take prende un numero e una lista ed estrae quel numero di elementi partendo dall’inizio della lista:

ghci> take 3 [5,4,3,2,1]
[5,4,3]
ghci> take 1 [3,9,3]
[3]
ghci> take 5 [1,2]
[1,2]
ghci> take 0 [6,6,6]
[]

Se si tenta di estrarre più elementi di quanti ne contiene la lista, viene restituita l’intera lista. Se si estraggono 0 elementi, si ottiene una lista vuota.

Sintassi: funzione drop

La funzione drop prende un numero e una lista e scarta il numero di elementi specificato dall’inizio della lista:

ghci> drop 3 [8,4,2,1,5,6]
[1,5,6]
ghci> drop 0 [1,2,3,4]
[1,2,3,4]
ghci> drop 100 [1,2,3,4]
[]

Sintassi: funzioni maximum e minimum

La funzione maximum prende una lista di elementi ordinabili e restituisce l’elemento più grande, mentre la funzione minimum restituisce l’elemento più piccolo:

ghci> minimum [8,4,2,1,5,6]
1
ghci> maximum [1,9,2,3,4]
9

Sintassi: funzioni sum e product

La funzione sum prende una lista di numeri e restituisce la loro somma, mentre la funzione product prende una lista di numeri e restituisce il loro prodotto:

ghci> sum [5,2,1,6,3,2,5,7]
31
ghci> product [6,2,1,2]
24
ghci> product [1,2,5,6,7,9,2,0]
0

Sintassi: funzione elem

La funzione elem prende un elemento e una lista e ci dice se quell’elemento è presente nella lista:

ghci> elem 4 [3,4,5,6]
True

Solitamente viene usata come funzione infissa perché è più leggibile:

ghci> 10 `elem` [3,4,5,6]
False

2.2 - Range list

In Haskell, quando vogliamo creare una lista di numeri che seguono una sequenza aritmetica, non è necessario scrivere manualmente ogni elemento. Le range list sono una sintassi conveniente per generare liste di elementi enumerabili, come numeri e caratteri. Un elemento è enumerabile se fa parte di una sequenza ordinata (i numeri naturali, le lettere dell’alfabeto, ecc.), mentre nomi arbitrari non lo sono.

Sintassi: range list

In Haskell una range list è una lista definita attraverso un intervallo, cioè usando la sintassi [inizio..fine] per creare una lista di elementi contenente tutti i valori compresi tra il valore inizio e il valore fine, compresi questi ultimi:

ghci> [1..20]
[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
ghci> ['a'..'z']
"abcdefghijklmnopqrstuvwxyz"
ghci> ['K'..'Z']
"KLMNOPQRSTUVWXYZ"

Per specificare un passo (incremento) diverso da 1, scrivi i primi due elementi separati da virgola, seguito da .. e dal limite superiore: [primo,secondo..fine]:

ghci> [2,4..20]
[2,4,6,8,10,12,14,16,18,20]
ghci> [3,6..20]
[3,6,9,12,15,18]

Per creare liste decrescenti, specifica esplicitamente il passo negativo:

ghci> [20,19..1]
[20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1]

Omettendo il limite superiore, puoi creare liste infinite. Haskell le valuta in modo lazy, quindi calcola solo gli elementi di cui hai bisogno:

ghci> take 24 [13,26..]
[13,26,39,52,65,78,91,104,117,130,143,156,169,182,195,208,221,234,247,260,273,286]

Attenzione: limitazioni delle range list

Definendo una range list è possibile solo specificare un incremento costante (cioè ottenere sequenze aritmetiche), non progressioni più complesse come potenze.

È consigliato inoltre evitare intervalli con numeri in virgola mobile, poiché la loro imprecisione può produrre risultati inaspettati:

ghci> [0.1, 0.3 .. 1]
[0.1,0.3,0.5,0.7,0.8999999999999999,1.0999999999999999]

2.3 - Liste infinite

Sintassi: funzione cycle

La funzione cycle prende una lista e la ripete infinitamente. Poiché il risultato è una lista infinita, bisogna sempre troncarla usando funzioni come take per ottenere un numero finito di elementi:

ghci> take 10 (cycle [1,2,3])
[1,2,3,1,2,3,1,2,3,1]
ghci> take 12 (cycle "LOL ")
"LOL LOL LOL "

Sintassi: funzione repeat

La funzione repeat prende un elemento e produce una lista infinita contenente solo quel elemento. È equivalente a ciclare una lista con un solo elemento:

ghci> take 10 (repeat 5)
[5,5,5,5,5,5,5,5,5,5]

Sintassi: funzione replicate

La funzione replicate prende un numero e un elemento, e restituisce una lista finita contenente quel numero di copie dell’elemento. È conveniente usarla al posto di repeat quando si sa esattamente quanti elementi servono:

ghci> replicate 3 10
[10,10,10]

2.4 - List comprehension

Un modo molto più comodo per definire una lista contenente solo elementi che rispettano un certo criterio è la list comprehension.

Sintassi: list comprehension

In Haskell una list comprehension è una lista definita attraverso una funzione applicata a ogni elemento di un’altra lista passata in input. Ha la forma: [funzione_output | variabile <- lista_input]

ghci> [x*2 | x <- [1..10]]
[2,4,6,8,10,12,14,16,18,20]

In questo esempio, x*2 è la funzione di output, x <- [1..10] estrae ogni elemento dalla lista [1..10].

Sintassi: list comprehension con predicati

Una list comprehension può contenere predicati che filtrano gli elementi usando una o più condizioni, separate da virgole:

ghci> [x*2 | x <- [1..10], x*2 >= 12]
[12,14,16,18,20]
ghci> [x | x <- [50..100], x `mod` 7 == 3]
[52,59,66,73,80,87,94]

Nel primo esempio, vengono raddoppiati solo gli elementi il cui doppio è maggiore o uguale a 12. Nel secondo, vengono selezionati solo i numeri tra 50 e 100 il cui resto della divisione per 7 è 3.

Possono anche esserci più predicati separati da virgole. Un elemento viene incluso solo se soddisfa tutti i predicati:

ghci> [ x | x <- [10..20], x /= 13, x /= 15, x /= 19]
[10,11,12,14,16,17,18,20]

In questo esempio, vengono selezionati solo i numeri tra 10 e 20 che non sono 13, 15 o 19.

Sintassi: list comprehension con condizionali

Una list comprehension può contenere espressioni condizionali (if-then-else) nella funzione di output per trasformare gli elementi in base a condizioni:

boomBangs xs = [ if x < 10 then "BOOM!" else "BANG!" | x <- xs, odd x]
ghci> boomBangs [7..13]
["BOOM!","BOOM!","BANG!","BANG!"]

In questo esempio, la funzione odd verifica se un numero è dispari. L’elemento viene incluso nella lista solo se il predicato odd x è True.

Sintassi: list comprehension con più liste

Una list comprehension può ottenere dati da più liste. Produce tutte le combinazioni possibili degli elementi delle liste, applicate alla funzione di output:

ghci> [ x*y | x <- [2,5,10], y <- [8,10,11]]
[16,20,22,40,50,55,80,100,110]
ghci> [ x*y | x <- [2,5,10], y <- [8,10,11], x*y > 50]
[55,80,100,110]

Si possono anche combinare stringhe (ossia liste di caratteri):

ghci> nouns = ["hobo","frog","pope"]
ghci> adjectives = ["lazy","grouchy","scheming"]
ghci> [adjective ++ " " ++ noun | adjective <- adjectives, noun <- nouns]
["lazy hobo","lazy frog","lazy pope","grouchy hobo","grouchy frog","grouchy pope","scheming hobo","scheming frog","scheming pope"]

Sintassi: list comprehension annidate

Una list comprehension annidata è una list comprehension che contiene altre list comprehension al suo interno. Questo permette di operare su liste annidate e filtrarne gli elementi senza appiattire la struttura:

ghci> let xxs = [[1,3,5,2,3,1,2,4,5],[1,2,3,4,5,6,7,8,9],[1,2,4,2,1,6,3,1,3,2,3,6]]
ghci> [ [ x | x <- xs, even x ] | xs <- xxs]
[[2,2,4],[2,4,6,8],[2,4,2,6,2,6]]

In questo esempio, la list comprehension esterna itera su ogni lista in xxs (estraendo ogni sottolista xs), mentre quella interna filtra i numeri pari da ogni lista xs, mantenendo la struttura annidata. Il risultato è una lista di liste contenente solo i numeri pari da ogni sottolista.

3 - Tuple

Le tupla sono un modo per memorizzare più valori in un’unica struttura, simile alle liste, ma la loro dimensione e i tipi dei loro componenti sono fissi e definiti al momento della creazione.

Definizione: tupla in Haskell

In Haskell, una tupla è una struttura dati eterogenea che combina un numero fisso e noto di valori, chiamati componenti. È denotata con parentesi tonde () contenente i suoi componenti separati da virgole:

ghci> (1, 2)
(1,2)
ghci> ("David", "Lynch", 55)
("David","Lynch",55)
ghci> (1, "hello", True)
(1,"hello",True)

Una tupla di due elementi si chiama coppia (in inglese pair), una di tre elementi si chiama tripla (in inglese triple) e così via.

Osservazione: il tipo delle tuple dipende da numero e tipi dei componenti

Il tipo di una tupla è determinato da due fattori: il numero di componenti e i tipi di ciascun componente. Questo significa che ogni tupla di diversa dimensione o con diversi tipi è considerata un tipo completamente diverso. Per esempio:

  • (1, 2) è di tipo (Integer, Integer),
  • (1, 2, 3) è di tipo (Integer, Integer, Integer),
  • (1, "hello") è di tipo (Integer, String) e
  • ("David", "Lynch", 55) è di tipo (String, String, Integer).

Osservazione: tuple e liste

A differenza delle liste, le tuple:

  • Hanno una dimensione fissa, nota al momento della creazione.
  • Possono contenere elementi di tipi diversi (sono cioè strutture dati eterogenee).
  • Il loro tipo dipende dal numero di componenti e dai loro tipi, come già osservato poco fa.

Proprio per quest’ultimo punto, se una lista ha come elementi delle tuple, esse devono avere componenti dello stesso tipo e dello stesso numero. Non possiamo avere una lista mista di coppie e triple, né puoi possiamo una lista che contenga sia (1,2) che (1,"hello") perché il primo elemento sarebbe una coppia di numeri e il secondo sarebbe una coppia di numero e stringa:

ghci> [(1,2), (8,11,5), (4,5)]
<interactive>:5:9: error: [GHC-83865]
 Couldn't match expected type: (a, b)
                  with actual type: (a0, b0, c0)
 In the expression: (8, 11, 5)
      In the expression: [(1, 2), (8, 11, 5), (4, 5)]
      In an equation for it': it = [(1, 2), (8, 11, 5), (4, 5)]
 Relevant bindings include
        it :: [(a, b)] (bound at <interactive>:5:1)

Attenzione: non esiste la tupla singoletto

In Haskell non esiste il concetto di tupla singoletto, cioè con un solo elemento: una tupla di un elemento sarebbe semplicemente il valore stesso, quindi non avrebbe utilità pratica.

3.1 - Funzioni su tuple

Ora vediamo un po’ di funzioni che agiscono sulle tuple.

Definizione: funzione fst

fst è una funzione di Haskell che prende una coppia e restituisce il suo primo componente:

ghci> fst (9,11)
8
ghci> fst ("Wow", False)
"Wow"

Questa funzione opera solo su coppie, cioè tuple di 2 componenti, non su triple o tuple più grandi.

Definizione: funzione snd

snd è una funzione di Haskell che prende una coppia e restituisce il suo secondo componente:

ghci> snd (8,11)
11
ghci> snd ("Wow", False)
False

Questa funzione opera solo su coppie, cioè tuple di 2 componenti, non su triple o tuple più grandi.

Definizione: funzione zip

zip è una funzione di Haskell che prende due liste e le “cuce” insieme (come una cerniera lampo, cioè in inglese zip), creando una lista di coppie con gli elementi corrispondenti abbinati a due a due:

ghci> zip [1,2,3,4,5] [5,5,5,5,5]
[(1,5),(2,5),(3,5),(4,5),(5,5)]
ghci> zip [1 .. 5] ["one", "two", "three", "four", "five"]
[(1,"one"),(2,"two"),(3,"three"),(4,"four"),(5,"five")]

Se le liste hanno lunghezze diverse, la lista più lunga viene troncata per corrispondere alla lunghezza di quella più corta:

ghci> zip [5,3,2,6,2,7,2,5,4,6,6] ["im","a","turtle"]
[(5,"im"),(3,"a"),(2,"turtle")]

Osservazione: zip tra una lista finita e una infinita

Poiché Haskell è lazy, è possibile fare una zip tra una lista finita e una infinita, perché quest’ultima verrà valutata solo finché serve (cioè per un numero di componenti pari a quello della lista finita):

ghci> zip [1..] ["apple", "orange", "cherry", "mango"]
[(1,"apple"),(2,"orange"),(3,"cherry"),(4,"mango")]

Fonti

  • 📚 Miran Lipovača, Learn You a Haskell for Great Good!: