Haskell Language
Classes de types
Recherche…
Introduction
Les classes de caractères dans Haskell permettent de définir le comportement associé à un type séparément de la définition de ce type. Alors que, par exemple, en Java, vous définissez le comportement comme faisant partie de la définition du type - c'est-à-dire que dans une interface, une classe abstraite ou une classe concrète - Haskell garde ces deux choses séparées.
Un certain nombre de classes de caractères sont déjà définies dans le package de base
d'Haskell. La relation entre ceux-ci est illustrée dans la section Remarques ci-dessous.
Remarques
Le diagramme suivant tiré de l'article de Typeclassopedia montre la relation entre les différentes classes de caractères de Haskell.
Peut-être et la classe Functor
Dans Haskell, les types de données peuvent avoir des arguments comme les fonctions. Prenez le type Maybe
par exemple.
Maybe
- Maybe
est-ce un type très utile qui nous permet de représenter l’idée de l’échec ou de sa possibilité. En d'autres termes, si un calcul risque d'échouer, nous utiliserons le type Maybe
ici. Maybe
agit comme un wrapper pour d'autres types, en leur donnant des fonctionnalités supplémentaires.
Sa déclaration actuelle est assez simple.
Maybe a = Just a | Nothing
Ce que cela raconte, c'est qu'un Maybe
présente sous deux formes, un Just
, qui représente le succès, et un Nothing
, qui représente un échec. Just
un argument qui détermine le type de l' Maybe
, et Nothing
n'en prend aucun. Par exemple, la valeur Just "foo"
aura le type Maybe String
, qui est un type de chaîne encapsulé avec la fonctionnalité Maybe
supplémentaire. La valeur Nothing
est de type Maybe a
- a
Maybe a
où a
peut être de tout type.
Cette idée d'encapsuler les types pour leur donner des fonctionnalités supplémentaires est très utile et s'applique à plus que Maybe
- Maybe
. D'autres exemples incluent les types Either
, IO
et list, chacun offrant des fonctionnalités différentes. Cependant, certaines actions et capacités sont communes à tous ces types d'encapsuleurs. Le plus notable est la possibilité de modifier la valeur encapsulée.
Il est courant de penser à ces types de types comme des boîtes pouvant contenir des valeurs. Différentes boîtes contiennent des valeurs différentes et font des choses différentes, mais aucune n'est utile sans pouvoir accéder au contenu.
Pour encapsuler cette idée, Haskell est livré avec une classe de caractères standard, nommée Functor
. Il est défini comme suit.
class Functor f where
fmap :: (a -> b) -> f a -> f b
Comme on peut le voir, la classe a une seule fonction, fmap
, de deux arguments. Le premier argument est une fonction d'un type, d' a
à l'autre, b
. Le second argument est un foncteur (type wrapper) contenant une valeur de type a
. Il retourne un foncteur (type wrapper) contenant une valeur de type b
.
En termes simples, fmap
prend une fonction et s'applique à la valeur à l'intérieur d'un foncteur. C'est la seule fonction nécessaire pour qu'un type soit membre de la classe Functor
, mais c'est extrêmement utile. Les classes fonctionnant sur des foncteurs ayant des applications plus spécifiques peuvent être trouvées dans les classes de types Applicative
et Monad
.
Classe d'héritage: Classe de type Ord
Haskell supporte une notion d'extension de classe. Par exemple, la classe Ord
hérite de toutes les opérations dans Eq
, mais a en outre une fonction de compare
qui renvoie une Ordering
entre les valeurs. Ord
peut également contenir les opérateurs de comparaison de commandes courants, ainsi qu'une méthode min
et une méthode max
.
La notation =>
a la même signification que dans une signature de fonction et nécessite le type a
pour implémenter Eq
, afin de mettre en œuvre Ord
.
data Ordering = EQ | LT | GT
class Eq a => Ord a where
compare :: Ord a => a -> a -> Ordering
(<) :: Ord a => a -> a -> Bool
(<=) :: Ord a => a -> a -> Bool
(>) :: Ord a => a -> a -> Bool
(>=) :: Ord a => a -> a -> Bool
min :: Ord a => a -> a -> a
max :: Ord a => a -> a -> a
Toutes les méthodes suivantes peuvent être compare
de différentes manières:
x < y = compare x y == LT
x <= y = x < y || x == y -- Note the use of (==) inherited from Eq
x > y = not (x <= y)
x >= y = not (x < y)
min x y = case compare x y of
EQ -> x
LT -> x
GT -> y
max x y = case compare x y of
EQ -> x
LT -> y
GT -> x
Les classes de type qui étendent elles-mêmes Ord
doivent implémenter au moins la méthode compare
ou la méthode (<=)
elles-mêmes, ce qui crée le réseau d'héritage dirigé.
Eq
Tous les types de données de base (comme Int
, String
, Eq a => [a]
) de Prelude sauf pour les fonctions et IO
ont des instances de Eq
. Si un type instancie Eq
cela signifie que nous savons comparer deux valeurs pour une valeur ou une égalité structurelle .
> 3 == 2
False
> 3 == 3
True
Méthodes requises
-
(==) :: Eq a => a -> a -> Boolean
ou(/=) :: Eq a => a -> a -> Boolean
(si un seul est implémenté, l’autre utilise par défaut la négation du défini un)
Définit
-
(==) :: Eq a => a -> a -> Boolean
-
(/=) :: Eq a => a -> a -> Boolean
Superclasses directes
Aucun
Sous-classes notables
Ord
Les types instanciant Ord
incluent, par exemple, Int
, String
et [a]
(pour les types a
où il y a une instance Ord a
). Si un type instancie Ord
cela signifie que nous connaissons un ordre «naturel» de valeurs de ce type. Notez qu'il y a souvent beaucoup de choix possibles pour un ordre "naturel" d'un type et Ord
nous oblige à en privilégier un.
Ord
fournit les opérateurs standard (<=)
, (<)
, (>)
, (>=)
mais les définit de manière intéressante en utilisant un type de données algébrique personnalisé
data Ordering = LT | EQ | GT
compare :: Ord a => a -> a -> Ordering
Méthodes requises
-
compare :: Ord a => a -> a -> Ordering
ou(<=) :: Ord a => a -> a -> Boolean
(la méthode decompare
par défaut du standard utilise(<=)
dans son implémentation)
Définit
-
compare :: Ord a => a -> a -> Ordering
-
(<=) :: Ord a => a -> a -> Boolean
-
(<) :: Ord a => a -> a -> Boolean
-
(>=) :: Ord a => a -> a -> Boolean
-
(>) :: Ord a => a -> a -> Boolean
-
min :: Ord a => a -> a -> a
-
max :: Ord a => a -> a -> a
Superclasses directes
Monoïde
Les types d'instanciation de Monoid
incluent, entre autres, des listes, des nombres et des fonctions avec des valeurs de retour Monoid
. Pour instancier un Monoid
un type doit prendre en charge une opération binaire associative ( mappend
ou (<>)
) qui combine ses valeurs et avoir une valeur "zéro" spéciale ( mempty
) telle que combiner une valeur avec elle ne change pas cette valeur:
mempty <> x == x
x <> mempty == x
x <> (y <> z) == (x <> y) <> z
Intuitivement, les types de Monoid
sont "similaires à la liste" dans la mesure où ils prennent en charge les valeurs ajoutées ensemble. Alternativement, les types Monoid
peuvent être considérés comme des séquences de valeurs pour lesquelles nous nous préoccupons de l'ordre mais pas du groupement. Par exemple, un arbre binaire est un Monoid
, mais en utilisant les opérations Monoid
, nous ne pouvons pas voir sa structure de branchement, seulement une traversée de ses valeurs (voir Foldable
et Traversable
).
Méthodes requises
-
mempty :: Monoid m => m
-
mappend :: Monoid m => m -> m -> m
Superclasses directes
Aucun
Num
La classe la plus générale pour les types de nombres, plus précisément pour les anneaux , c'est-à-dire les nombres pouvant être ajoutés et soustraits et multipliés au sens habituel, mais pas nécessairement divisés.
Cette classe contient à la fois des types intégraux ( Int
, Integer
, Word32
etc.) et des types fractionnaires ( Double
, Rational
, ainsi que des nombres complexes, etc.). Dans le cas des types finis, la sémantique est généralement comprise comme une arithmétique modulaire , c'est-à-dire avec un dépassement et un débordement † .
Notez que les règles pour les classes numériques sont beaucoup moins strictement respectées que les lois monades ou monoïdes, ou celles pour la comparaison d'égalité . En particulier, les nombres à virgule flottante obéissent généralement aux lois uniquement dans un sens approximatif.
Les méthodes
fromInteger :: Num a => Integer -> a
. convertir un entier au type de nombre général (en entourant la plage, si nécessaire). Les littéraux numériques de Haskell peuvent être compris comme un littéralInteger
monomorphe avec la conversion générale qui l'entoure. Vous pouvez donc utiliser le littéral5
à la fois dans un contexteInt
et un paramètreComplex Double
.(+) :: Num a => a -> a -> a
. Ajout standard, généralement compris comme associatif et commutatif, c'est-à-direa + (b + c) ≡ (a + b) + c a + b ≡ b + a
(-) :: Num a => a -> a -> a
. Soustraction, qui est l'inverse de l'addition:(a - b) + b ≡ (a + b) - b ≡ a
(*) :: Num a => a -> a -> a
. Multiplication, une opération associative distributive par rapport à l'addition:a * (b * c) ≡ (a * b) * c a * (b + c) ≡ a * b + a * c
pour les cas les plus courants, la multiplication est également commutative, mais ce n'est certainement pas une exigence.
negate :: Num a => a -> a
. Le nom complet de l'opérateur de négation unaire.-1
est le sucre syntaxique pournegate 1
.-a ≡ negate a ≡ 0 - a
abs :: Num a => a -> a
. La fonction de valeur absolue donne toujours un résultat non négatif de la même amplitudeabs (-a) ≡ abs a abs (abs a) ≡ abs a
abs a ≡ 0
ne devrait arriver que sia ≡ 0
.Pour les types réels, il est clair ce que signifie non négatif: vous avez toujours
abs a >= 0
. Les types complexes, etc., n'ont pas un ordre bien défini, mais le résultat desabs
doit toujours se situer dans le sous-ensemble réel ‡ (c.-à-d. Donner un nombre qui pourrait aussi être écrit sous forme de littéral sans négation).signum :: Num a => a -> a
. La fonction de signe, selon le nom, ne donne que-1
ou1
, en fonction du signe de l'argument. En fait, c'est seulement vrai pour les nombres réels non nuls; en général, lasignum
est mieux comprise comme la fonction de normalisation :abs (signum a) ≡ 1 -- unless a≡0 signum a * abs a ≡ a -- This is required to be true for all Num instances
Notez que la section 6.4.4 du rapport Haskell 2010 exige explicitement que cette dernière égalité soit valide pour toute instance
Num
valide.
Certaines bibliothèques, notamment les bibliothèques linéaires et hmatrix , ont une compréhension beaucoup plus laxiste de la classe Num
: elles la traitent simplement comme un moyen de surcharger les opérateurs arithmétiques . Bien que ce soit assez simple pour +
et -
, cela devient déjà gênant avec *
et plus encore avec les autres méthodes. Par exemple, devrait-on dire *
multiplication par matrice ou multiplication par éléments?
Il est sans doute une mauvaise idée de définir de telles instances sans nombre; Veuillez considérer des classes dédiées telles que VectorSpace
.
† En particulier, les «négatifs» des types non signés sont entourés d'un grand positif, par exemple (-4 :: Word32) == 4294967292
.
‡ Ce n'est pas le cas : les types de vecteurs n'ont pas de sous-ensemble réel. La controverse Num
-instances pour ces types définissent généralement abs
et signum
élément par élément, qui mathématiquement parlant ne fait pas vraiment de sens.