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.


Come già detto, Haskell ha un sistema di tipi statico. Ciò significa che il tipo di ogni espressione è noto a tempo di compilazione, il che ci porta ad avere un codice piÚ sicuro. Se scrivi un programma in cui provi a dividere un booleano con qualche numero, non ti permette neanche di compilare. Ciò ci va piÚ che bene perchÊ è meglio catturare questo tipo di errori a tempo di compilazione anzichÊ far crashare il progrmma. Ogni cosa in Haskell ha un tipo.

Definizione: tipo in Haskell

In Haskell, un tipo è un’etichetta che descrive quali valori può assumere un’espressione. Haskell ha una tipizzazione statica, cioè il tipo dell’espressione viene determinato a tempo di compilazione, ed è fortemente tipizzato, cioè non permette a una espressione di cambiare tipo durante l’esecuzione del programma.

Haskell gode inoltre dell’inferenza di tipo.

Definizione: comando :t nel GHCi

:t (abbreviazione di :type) è un comando di GHCi che mostra la firma di tipo di un’espressione senza valutarla.

Now we’ll use GHCi to examine the types of some expressions. We’ll do that by using the :t command which, followed by any valid expression, tells us its type. Let’s give it a whirl.

ghci> :t 'a'  
'a' :: Char  
ghci> :t True  
True :: Bool  
ghci> :t "HELLO!"  
"HELLO!" :: [Char]  
ghci> :t (True, 'a')  
(True, 'a') :: (Bool, Char)  
ghci> :t 4 == 5  
4 == 5 :: Bool  

Here we see that doing :t on an expression prints out the expression followed by :: and its type. :: is read as “has type of”. Explicit types are always denoted with the first letter in capital case. 'a', as it would seem, has a type of Char. It’s not hard to conclude that it stands for character. True is of a Bool type. That makes sense. But what’s this? Examining the type of "HELLO!" yields a [Char]. The square brackets denote a list. So we read that as it being a list of characters. Unlike lists, each tuple length has its own type. So the expression of (True, 'a') has a type of (Bool, Char), whereas an expression such as ('a','b','c') would have the type of (Char, Char, Char). 4 == 5 will always return False, so its type is Bool.

Functions also have types. When writing our own functions, we can choose to give them an explicit type declaration. This is generally considered to be good practice except when writing very short functions. From here on, we’ll give all the functions that we make type declarations. Remember the list comprehension we made previously that filters a string so that only caps remain? Here’s how it looks like with a type declaration.

removeNonUppercase :: [Char] -> [Char]  
removeNonUppercase st = [c | c <- st, c `elem` ['A'..'Z']]  

removeNonUppercase has a type of [Char] -> [Char], meaning that it maps from a string to a string. That’s because it takes one string as a parameter and returns another as a result. The [Char] type is synonymous with String so it’s clearer if we write removeNonUppercase :: String -> String. We didn’t have to give this function a type declaration because the compiler can infer by itself that it’s a function from a string to a string but we did anyway. But how do we write out the type of a function that takes several parameters? Here’s a simple function that takes three integers and adds them together:

addThree :: Int -> Int -> Int -> Int  
addThree x y z = x + y + z  

The parameters are separated with -> and there’s no special distinction between the parameters and the return type. The return type is the last item in the declaration and the parameters are the first three. Later on we’ll see why they’re all just separated with -> instead of having some more explicit distinction between the return types and the parameters like Int, Int, Int -> Int or something.

If you want to give your function a type declaration but are unsure as to what it should be, you can always just write the function without it and then check it with :t. Functions are expressions too, so :t works on them without a problem.

Here’s an overview of some common types.

Definizione: tipo Int

Int è un tipo in Haskell usato per rappresentare i numeri interi (in inglese integer numbers). È un tipo bounded e sulle macchine a 32 bit l’intervallo di valori corrisponde a .

Definizione: tipo Integer

Integer è un tipo in Haskell usato per rappresentare i numeri interi (in inglese integer numbers) ma, a differenza del tipo Int, non è bounded e, quindi, può essere usato per rappresentare numeri molto piÚ grandi o piÚ piccoli.

Definizione: tipo Float

Float è un tipo in Haskell usato per rappresentare i numeri reali a virgola mobile (in inglese real floating point) con precisione singola.

Definizione: tipo Double

Double è un tipo in Haskell usato per rappresentare i numeri reali a virgola mobile (in inglese real floating point) con precisione doppia (da qui il nome del tipo).

Definizione: tipo Bool

Bool è un tipo in Haskell usato per rappresentare i valori booleani di verità. Può assumere solo i valori True e False.

Definizione: tipo Char

Char è un tipo in Haskell usato per rappresentare i caratteri, delimitati da singole virgolette ''.

Variabili di tipi

Secondo te qual è il tipo della funzione head? Non è una domanda banale, perchÊ head prende una lista di un tipo qualsiasi ne restituisce il primo elemento, quindi quale tipo assume? Controlliamo su GHCi:

ghci> :t head  
head :: [a] -> a  

Hmmm! What is this a? Is it a type? Remember that we previously stated that types are written in capital case, so it can’t exactly be a type. Because it’s not in capital case it’s actually a type variable. That means that a can be of any type. This is much like generics in other languages, only in Haskell it’s much more powerful because it allows us to easily write very general functions if they don’t use any specific behavior of the types in them. Functions that have type variables are called polymorphic functions. The type declaration of head states that it takes a list of any type and returns one element of that type.

Definizione: variabile di tipo

In Haskell una variabile di tipo è un segnaposto generico nel tipo di una funzione o struttura datie generalmente si indica con una singola lettera minuscola (a, b, c, …).

Definizione: funzione polimorfica

In Haskell una funzione polimorfica è una funzione che nel suo tipo contiene variabili di tipo.

Remember fst? It returns the first component of a pair. Let’s examine its type.

ghci> :t fst  
fst :: (a, b) -> a  

We see that fst takes a tuple which contains two types and returns an element which is of the same type as the pair’s first component. That’s why we can use fst on a pair that contains any two types. Note that just because a and b are different type variables, they don’t have to be different types. It just states that the first component’s type and the return value’s type are the same.

Classi di tipi

Definizione: classe di tipo

In Haskell una classe di tipo è un insieme di tipi che condividono un insieme di operazioni.

Qual è il tipo della funzione ==? Controlliamo su GHCi:

ghci> :t (==)  
(==) :: (Eq a) => a -> a -> Bool  

Interesting. We see a new thing here, the => symbol. Everything before the => symbol is called a class constraint. We can read the previous type declaration like this: the equality function takes any two values that are of the same type and returns a Bool. The type of those two values must be a member of the Eq class (this was the class constraint).

Definizione: vincolo di classe

In Haskell un vincolo di classe è un’affermazione che precede il tipo di una funzione e dichiara quali classi di tipo una variabile di tipo deve soddisfare perché la funzione possa essere usata.

Nella firma di tipo è inserito tra l’operatore :: e il tipo (da cui è separato dall’operatore =>):

(==) :: (Eq a) => a -> a -> Bool

In questo caso indica che la funzione == contiene nel suo tipo una variabile di tipo a che però deve appartenere alla classe di tipo Eq.

Vediamo alcune classi di tipo.

Definizione: classe di tipo Eq

Eq è una classe di tipo su cui è definita l’uguaglianza strutturale e appartiene a essa qualsiasi tipo i cui valori possono essere confrontati tra loro con le funzioni == e /=.

Definizione: classe di tipo Ord

Ord è una classe di tipo a cui appartiene qualsiasi tipo i cui valori possono essere ordinati tra loro, per esempio usando gli operatori >, <, >= e <=.

Definizione: tipo Ordering

Ordering è un tipo in Haskell usato per rappresentare il confronto tra due valori. Può assumere solo i seguenti tre valori:

  • LT: sta per less than (in italiano minore di) e indica che il primo valore è minore del secondo.
  • EQ: sta per equal to (in italiano uguale a) e indica che i due valori sono uguali.
  • GT: sta per greater than (in italiano maggiore di) e indica che il primo valore è maggiore del secondo.

Viene usato principalmente nella funzione compare.

Definizione: funzione compare

La funzione compare prende due valori dello stesso tipo appartenente alla classe di tipo Ord e restituisce un Ordering che descrive la relazione d’ordine tra i due:

ghci> compare 3 5
LT
ghci> compare 5 5
EQ
ghci> compare 7 5
GT

Definizione: classe di tipo Show

Show è una classe di tipo a cui appartiene qualsiasi tipo i cui valori possono essere convertiti in una stringa, per esempio tramite la funzione show.

Definizione: classe di tipo Read

Read è una classe di tipo a cui appartiene qualsiasi tipo i cui valori possono essere ottenuti dal parsing di una stringa, per esempio tramite la funzione read. È in un certo senso l’inverso di Show.

Definizione: funzione show

La funzione show prende un valore di qualsiasi tipo appartenente alla classe di tipo Show e restituisce la sua rappresentazione come stringa:

ghci> show 42
"42"
ghci> show 3.14
"3.14"
ghci> show True
"True"

Definizione: funzione read

La funzione read prende una stringa e la converte in un valore di qualsiasi tipo appartenente alla classe di tipo Read. È l’inverso di show e richiede spesso un’annotazione di tipo esplicita per disambiguare il tipo restituito:

ghci> read "42" :: Int
42
ghci> read "3.14" :: Double
3.14
ghci> read "True" :: Bool
True

Definizione: classe di tipo Enum

Enum è una classe di tipo a cui appartiene qualsiasi tipo i cui valori sono enumerabili, ovvero hanno un predecessore e un successore ben definiti. Questo permette di usarli nelle range notation delle liste, come [LT .. GT] o ['a' .. 'z'].


Definizione: classe di tipo Bounded

Bounded è una classe di tipo a cui appartiene qualsiasi tipo che ha un valore minimo e un valore massimo, accessibili tramite le funzioni minBound e maxBound.


Definizione: classe di tipo Num

Num è una classe di tipo a cui appartiene qualsiasi tipo numerico i cui valori supportano le operazioni aritmetiche di base, come +, - e *.


Definizione: classe di tipo Integral

Integral è una classe di tipo a cui appartiene qualsiasi tipo numerico intero, come Int e Integer. Estende Num e aggiunge la divisione intera e il modulo tramite le funzioni div e mod.


Definizione: classe di tipo Floating

Floating è una classe di tipo a cui appartiene qualsiasi tipo numerico in virgola mobile, come Float e Double. Estende Num e aggiunge operazioni matematiche avanzate come sqrt, sin, cos e **.


Fonti