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.

Relations entre les classes de type Haskell standard, Figure 1 telles que publiées dans Typeclassopedia.

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 aa 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 de compare 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éral Integer monomorphe avec la conversion générale qui l'entoure. Vous pouvez donc utiliser le littéral 5 à la fois dans un contexte Int et un paramètre Complex Double .

  • (+) :: Num a => a -> a -> a . Ajout standard, généralement compris comme associatif et commutatif, c'est-à-dire

      a + (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 pour negate 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 amplitude

      abs (-a) ≡ abs a
      abs (abs a) ≡ abs a
    

    abs a ≡ 0 ne devrait arriver que si a ≡ 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 des abs 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 ou 1 , en fonction du signe de l'argument. En fait, c'est seulement vrai pour les nombres réels non nuls; en général, la signum 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.



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow