Coppie e tuple
Una tupla è una sequenza ordinata e finita di elementi non necessariamente omogenei. Ovvero, gli elementi di una tupla non devono necessariamente avere tutti lo stesso tipo. In questa scheda illustriamo alcuni modi di costruire tuple e come definire funzioni che hanno tuple come argomenti.
Creazione diretta di tuple
Per creare una tupla è sufficiente elencarne gli elementi racchiusi tra parentesi tonde e separati da virgole:
(1, True) -- Coppia con elementi 1 e True
(False, 2.5, True) -- Tripla con elementi False, 2.5 e True
Il tipo di una tupla ha la forma $(T_1, T_2, \dots, T_n)$ dove $T_1$, …, $T_n$ sono i tipi delle sue componenti.
:type (2.5, True)
:type (False, not)
:type (False, 2, True)
:type (False, (2, True))
Notiamo che è possibile creare tuple che contengono funzioni così
come è possibile creare tuple che contengono altre tuple. Gli ultimi
due esempi mostrano che (False, 2, True)
e (False, (2, True))
sono profondamente diverse: la prima è una tripla; la seconda è una
coppia la cui seconda componente è un’altra coppia. Il fatto che
queste due tuple siano diverse significa anche che non è possibile
confrontarle con gli operatori ==
e /=
, che invece si aspettano
operandi dello stesso tipo:
(False, 2, True) /= (False, (2, True))
Pattern matching di tuple
È possibile accedere alle componenti di una tupla usando il pattern
matching, dando un nome alle componenti di una tupla che si vogliono
utilizzare. A titolo di esempio, rivisitiamo la funzione addizione
definita nella sezione sulle funzioni a più argomenti:
addizione :: (Int, Int) -> Int
addizione (x, y) = x + y
Il pattern (x, y)
utilizzato nella definizione di addizione
specifica (come del resto già indicato nel tipo di addizione
) che
la funzione si aspetta come argomento una coppia. In aggiunta, il
pattern specifica due nomi (x
e y
, scelti dal programmatore) che
possono essere usati nella parte destra della definizione laddove
necessario.
addizione (1, 2)
Nota: sebbene appaia naturale usare tuple per definire e applicare funzioni a più argomenti, al punto che la sintassi mostrata qui sopra coincide con quella adottata in linguaggi di programmazione più tradizionali, in Haskell è più semplice, più efficiente e più flessibile sfruttare il currying. Vedremo dei casi in cui è comodo raggruppare argomenti multipli di una funzione in una coppia, ma questi sono eccezioni che possono essere gestiti con tecniche ad hoc.
Esercizi
- Definire una funzione
scambia :: (Int, Int) -> (Int, Int)
che, applicata a una coppia di numeri interi, restituisca la coppia con le componenti scambiate.scambia :: (Int, Int) -> (Int, Int) scambia (x, y) = (y, x)
- Definire una funzione
ordina :: (Int, Int, Int) -> (Int, Int, Int)
che, applicata a una tripla di numeri interi, restituisca la tripla con le componenti ordinate in modo non decrescente.ordina :: (Int, Int, Int) -> (Int, Int, Int) ordina (a, b, c) | a > b = ordina (b, a, c) ordina (a, b, c) | b > c = ordina (a, c, b) ordina x = x
- Un numero complesso può essere rappresentato da una coppia
ordinata di numeri (per esempio, di tipo
Double
). Definire funzioni per sommare, negare e sottrarre numeri complessi.-- Definiamo un alias di tipo in modo che il nome Complesso sia -- una comoda abbreviazione per il tipo (Double, Double) type Complesso = (Double, Double) addizioneC :: Complesso -> Complesso -> Complesso addizioneC (a, b) (c, d) = (a + c, b + d) negazioneC :: Complesso -> Complesso negazioneC (a, b) = (negate a, b) sottrazioneC :: Complesso -> Complesso -> Complesso sottrazioneC x y = addizioneC x (negazioneC y)