Types et classes§

Types prédéfinis§

Divers types§

Listes§

Les listes sont très souvent utilisées dans les langages fonctionnels et sont de bons exemples pour expliquer divers concepts de ces langages.

Inspection des types§

Par la commande :t de GHCi, donnez le type de :

La réponse est de la forme :

valeur :: [ contexte => ] type

Types polymorphes§

Les types composés sont polymorphes quand ils décrivent une famille de types :

[a] désigne la famille des listes d’éléments de type a (le \((\forall a)\) est implicite).

Exemple : [1,2,3], ['a','b','c'], ["azerty", "qwerty"] sont de type [a], mais pas [2,'b'].

Le contexte qui précède le type indique quelles opérations on peut effectuer avec les valeurs de type a:

Messages d’erreur§

N’ayez pas peur des messages d’erreur. Ils sont longs car ils contiennent beaucoup d’information. Voici un exemple :

Prelude> 'x' ++ "foo"

<interactive>:1:1:
  Couldn't match expected type `[a0]' with actual type `Char'
  In the first argument of `(++)', namely 'x'
  In the expression: 'x' ++ "foo"
  In an equation for `it': it = 'x' ++ "foo"

Quelque chose devait être une liste ([a0]) mais est en fait un caractère (Char). Cette chose est le premier argument de (++), c’est-à-dire 'x'. Les lignes suivantes donnent un peu plus de contexte (it désigne la valeur de la dernière expression tapée dans GHCi).

Type défini par le développeur§

Synonymes§

Il est possible de définir des synonymes de types grâce à la déclaration type :

Déclaration data§

En Haskell, on peut définir des types algébriques par des déclarations data :

data constructeurDeType [params] = constructeurDeValeur [params]
                        [ | constructeurDeValeur2 [params] ... ]

Ce sont des unions (récursives) de types produits :

différentes sortes de type

Type somme vs type produit§

data CouleurCarte = Trefle | Carreau | Coeur | Pique
data ValeurCarte = Deux | Trois | Quatre | Cinq | Six | Sept |
                   Huit | Neuf | Dix | Valet | Dame | Roi | As
data Carte = DonneCarte ValeurCarte CouleurCarte

CouleurCarte et ValeurCarte sont deux types sommes, tandis que Carte est un type produit. Par exemple, DonneCarte As Trefle est une valeur de type Carte.

Enregistrements§

Un enregistrement est un type produit dans lequel on nomme les champs ; ces noms pouvant servir d’accesseur.

data Personne = DonnePersonne {nom::String, prenom::String}

p1 = DonnePersonne "Curry" "Haskell"
p2 = DonnePersonne {prenom="Alonzo", nom="Church"}
*Main> nom p1
"Curry"
*Main> nom p2
"Church"

Types récursifs§

Il est possible de se référer au type que l’on crée dans un des constructeurs de valeurs.

data IntList = Empty | AddToIntList Int IntList

Autrement dit, une valeur de type IntList est soit la valeur Empty soit la combinaison d’une valeur de type Int et d’une valeur de type IntList, ce qui permet de représenter une série de valeurs entières de taille quelconque.

emptyList = Empty
oneEltList = AddToIntList 5 emptyList
twoEltList = AddToIntList 2 oneEltList

Types polymorphes§

Il est possible de paramétrer le constructeur de type afin de définir un type polymorphe.

data MyList a = Empty | AddToMyList a (MyList a)

On obtient ainsi une série contenant un nombre quelconque de valeurs de type arbitraire (mais identique pour toutes les valeurs).

emptyList = Empty
oneEltIntList = AddToMyList 5 emptyList
twoEltIntList = AddToMyList 2 oneEltIntList
oneEltCharList = AddToMyList 'a' emptyList
twoEltCharList = AddToMyList 'e' oneEltCharList

Encore un type polymorphe§

data Point a = MakePt a a

Par exemple, à partir de tout type a, le constructeur de type Point définit Point a, le type des points cartésiens ayant a comme coordonnées.

Le constructeur de valeur MakePt :: a -> a -> Point a est une manière d’obtenir des valeurs de type Point a. Par exemple, MakePt 2.0 3.0 est le point \((2,3)\).

Avertissement§

Les constructeurs de type comme Point et les constructeurs de valeurs comme MakePt se trouvent dans des espaces de noms séparés. Il est donc possible de donner le même nom aux deux constructeurs, afin de rendre plus évident le lien entre un type et le constructeur de valeur associé.

data Point a = Point a a

Liste revisitée§

[] désigne à la fois un constructeur de valeur (liste vide) et un constructeur de type qui, à partir du type a, crée le type [] a (habituellement noté [a]), c-à-d. une liste de a.

La définition d’une liste est récursive :

data [a] = [] | a : [a]

Une liste de a est soit une liste vide, soit une liste ayant au moins une valeur de type a en tête de liste.

Tuple revisité§

Bien sûr, il est possible de définir un type polymorphe par plusieurs paramètres.

data Pair a b = MakePair a b
aPair = MakePair 5 'a'

C’est de cette manière que sont définis les tuples pour lesquels (,) désigne à la fois un constructeur de valeur (à paramètres) et un constructeur de type qui, à partir des types a et b, crée le type (,) a b (habituellement noté (a,b)).

data (,) a b = (,) a b

Autres types habituels§

Type unit

data () = ()

Type Maybe

data Maybe a = Nothing | Just a

Type Either

data Either a b = Left a | Right b

Derniers types pour la route§

Comparez

data Tree a = Leaf a | Node (Tree a) (Tree a)

et

data Tree a = Empty | Node (Tree a) a (Tree a)

Dessinez et construisez des valeurs pour ces types.

Classe et surcharge§

Surcharge§

La surcharge désigne le fait qu’une même opération puisse être appliquée à des types différents tout en menant à des comportements différents.

Les déclarations class et instance vont nous permettre de regrouper les types en classe selon les opérations qu’on peut leur appliquer.

Déclaration class§

Intuitivement, une classe correspond à un ensemble de type pour lesquels certaines opérations sont définies. Par exemple, la classe Eq du Prélude correspond à tous les types a pour lesquels l’opération (==) est définie.

class Eq a where
  (==), (/=) :: a -> a -> Bool
  x /= y = not (x == y)

Notez que l’opérateur (/=) est implémenté directement à partir de l’opérateur (==), qui est le point de variation. L’implémentation de cet opérateur devra être fourni par les types se déclarant comme instance de Eq.

Exemple§

Soit le type suivant :

data CouleurCarte = Trefle | Carreau | Coeur | Pique

On ne peut pas faire par défaut de test d’égalité, car l’opérateur (==) :: Eq a => a -> a -> Bool ne prend en paramètre que des types qui sont des instances de Eq.

*Main> Trefle == Pique

<interactive>:6:8:
No instance for (Eq CouleurCarte) arising from a use of ‘==’
In the expression: Trefle == Pique
In an equation for ‘it’: it = Trefle == Pique

Déclaration instance§

Un type déclaré comme instance d’une classe doit surcharger les opérations associées à la classe. Par exemple, pour pouvoir comparer deux couleurs de carte à jouer, il faut explicitement déclarer le type CouleurCarte comme instance de Eq et surcharger (==) :

instance Eq CouleurCarte where
  c1 == c2 = case (c1,c2) of
               (Trefle,Trefle) -> True
               (Carreau,Carreau) -> True
               (Coeur,Coeur) -> True
               (Pique,Pique) -> True
               (_,_) -> False
*Main> Trefle == Pique
False
*Main> Trefle /= Pique
True

Classes du Prélude§

Le Prélude a de nombreuses classes :

Obtenez la liste complète des opérations d’une classe et de ses instances avec la commande :i suivi du nom de la classe dans GHCi.

Hiérarchie de classe§

En Haskell, il est possible d’étendre une classe. Par exemple, la classe Ord hérite des opérations == et /= de Eq et possède en plus des opérations de comparaison, ainsi que les fonctions min et max. On dit que Ord est une sous-classe de Eq.

class (Eq a) => Ord a where
  compare              :: a -> a -> Ordering
  (<), (<=), (>=), (>) :: a -> a -> Bool
  max, min             :: a -> a -> a
  --compare est le point de variation
  --il suffit à définir tout le reste

Une fonction qui utilise les opérations de Eq et Ord peut utiliser le contexte (Ord a) plutôt que (Eq a, Ord a) car Ord “implique” Eq.

Exemple des nombres§

différentes classes de nombre

Conversion§

La fonction usuelle pour convertir une type entier (instance de Integral) en un type numérique (instance de Num) est :

fromIntegral :: (Num b, Integral a) => a -> b

Si n est de type Int, la conversion est nécessaire.

*Main> sqrt n

<interactive>:32:1:
   No instance for (Floating Int) arising from a use of ‘sqrt’
   In the expression: sqrt n
   In an equation for ‘it’: it = sqrt n
*Main> sqrt (fromIntegral n)

Clause deriving§

La déclaration de CouleurCarte comme instance de Eq est simple mais ennuyante à écrire. Heureusement, l’instance peut être déduite automatiquement d’une déclaration data si on spécifie la clause deriving comme dans l’exemple suivant :

data CouleurCarte = Trefle | Carreau | Coeur | Pique
  deriving (Eq, Ord, Enum, Read, Show)
data ValeurCarte = Deux | Trois | Quatre | Cinq | Six | Sept |
                   Huit | Neuf | Dix | Valet | Dame | Roi | As
  deriving (Eq, Ord, Enum, Read, Show)

Les instances de Ord, Enum, Read, Show peuvent aussi être générées automatiquement dans la même clause.

Opérations habituelles§

Que donnent les expressions suivantes ?

Conclusion§

Capacités/Connaissances§

Comparaison avec d’autres langages§

Opérations habituelles§

*Main> show Coeur
"Coeur"
*Main> :t read
read :: Read a => String -> a
*Main> read "Coeur" :: CouleurCarte
Coeur
*Main> pred As
Roi
*Main> succ As
*** Exception: succ{ValeurCarte}: tried to take `succ' of last tag
in enumeration
*Main> fromEnum Trefle
0
*Main> fromEnum Dame
10

Opérations habituelles (suite)§

*Main> enumFrom Valet
[Valet,Dame,Roi,As]
*Main> enumFrom Trefle
[Trefle,Carreau,Coeur,Pique]
*Main> enumFromTo Cinq Dix
[Cinq,Six,Sept,Huit,Neuf,Dix]
*Main> [Trefle ..]
[Trefle,Carreau,Coeur,Pique]
*Main> [Cinq ..]
[Cinq,Six,Sept,Huit,Neuf,Dix,Valet,Dame,Roi,As]
*Main> [Cinq .. Dix]
[Cinq,Six,Sept,Huit,Neuf,Dix]
*Main> toEnum 12 :: ValeurCarte
As