Ici, les "valeurs", les "types" et les "types" ont des significations formelles, donc compte tenu de leur utilisation courante en anglais ou de leurs analogies pour classer les automobiles, vous ne pourrez que faire jusqu'ici.
Ma réponse porte sur la signification formelle de ces termes dans le contexte de Haskell en particulier; ces significations sont basées sur (mais ne sont pas vraiment identiques à) les significations utilisées dans la "théorie des types" mathématique / CS. Donc, ce ne sera pas une très bonne réponse "informatique", mais cela devrait servir de très bonne réponse Haskell.
En Haskell (et dans d'autres langages), il s'avère utile d'affecter un type à une expression de programme qui décrit la classe de valeurs que l'expression est autorisée à avoir. Je suppose ici que vous avez vu suffisamment d'exemples pour comprendre pourquoi il serait utile de savoir que dans l'expression sqrt (a**2 + b**2)
, les variables a
et b
seront toujours des valeurs de type Double
et non, disons String
et Bool
respectivement. Fondamentalement, avoir des types nous aide à écrire des expressions / programmes qui fonctionneront correctement sur une large gamme de valeurs .
Maintenant, quelque chose que vous n'avez peut-être pas réalisé, c'est que les types Haskell, comme ceux qui apparaissent dans les signatures de type:
fmap :: Functor f => (a -> b) -> f a -> f b
sont en fait eux-mêmes écrits dans un sous-langage Haskell de niveau type. Le texte du programme Functor f => (a -> b) -> f a -> f b
est - littéralement - une expression de type écrite dans cette sous-langue. Le sous - langage comprend les opérateurs (par exemple, ->
est un opérateur infixe associatif droit dans cette langue), les variables (par exemple f
, a
et b
), et « application » d'une expression de type à un autre (par exemple, f a
est f
appliqué à a
).
Ai-je mentionné à quel point il était utile dans de nombreuses langues d'affecter des types à des expressions de programme pour décrire des classes de valeurs d'expression? Eh bien, dans cette sous-langue au niveau du type, les expressions évaluent les types (plutôt que les valeurs ) et il finit par être utile d'attribuer des types aux expressions de type pour décrire les classes de types qu'elles sont autorisées à représenter. Fondamentalement, avoir des types nous aide à écrire des expressions de type qui fonctionneront correctement sur un large éventail de types .
Ainsi, les valeurs sont des types comme les types sont des types , et les types nous aident à écrire des programmes de niveau valeur tandis que les types nous aident à écrire des programmes de niveau type .
À quoi ressemblent ces types ? Eh bien, considérez la signature de type:
id :: a -> a
Si l'expression de type a -> a
doit être valide, quel type de types devrions-nous autoriser a
à être variable ? Eh bien, les expressions de type:
Int -> Int
Bool -> Bool
semblent valides, donc les types Int
et Bool
sont évidemment du bon type . Mais des types encore plus compliqués comme:
[Double] -> [Double]
Maybe [(Double,Int)] -> Maybe [(Double,Int)]
semble valide. En fait, puisque nous devons pouvoir faire appel id
à des fonctions, même:
(a -> a) -> (a -> a)
semble bien. Ainsi, Int
, Bool
, [Double]
, Maybe [(Double,Int)]
et ils a -> a
ressemblent tous types du droit genre .
En d'autres termes, il semble qu'il n'y ait qu'un seul type , appelons-le *
comme un caractère générique Unix, et chaque type a le même type *
, fin de l'histoire.
Droite?
Enfin, pas tout à fait. Il s'avère que Maybe
, tout seul, est tout aussi valide une expression de type que Maybe Int
(de la même manière sqrt
, tout seul, est tout aussi valide une expression de valeur que sqrt 25
). Cependant , l'expression de type suivante n'est pas valide:
Maybe -> Maybe
Parce que, tandis que Maybe
est une expression de type, elle ne représente pas le type de type qui peut avoir des valeurs. C'est ainsi que nous devrions définir *
- c'est le type de types qui ont des valeurs; il inclut les types "complets" comme Double
ou Maybe [(Double,Int)]
mais exclut les types incomplets sans valeur comme Either String
. Pour simplifier, j'appellerai ces types complets de types *
"types concrets", bien que cette terminologie ne soit pas universelle, et "types concrets" pourraient signifier quelque chose de très différent, par exemple, d'un programmeur C ++.
Maintenant, dans l'expression de type a -> a
, tant que le type a
a genre *
(le genre de types de béton), le résultat de l'expression de type a -> a
sera également avoir type *
( à savoir le genre de types de béton).
Alors, quel genre de genre est Maybe
? Eh bien, Maybe
peut être appliqué à un type de béton pour donner un autre type de béton. Cela Maybe
ressemble donc un peu à une fonction de niveau type qui prend un type de type *
et renvoie un type de type *
. Si nous avions une fonction de niveau de valeur qui prenait une valeur de type Int
et renvoyait une valeur de type Int
, nous lui donnerions une signature de typeInt -> Int
, donc par analogie, nous devrions donner Maybe
une sorte de signature * -> *
. GHCi accepte:
> :kind Maybe
Maybe :: * -> *
Revenir à:
fmap :: Functor f => (a -> b) -> f a -> f b
Dans ce type de signature, variable f
a kind * -> *
et variables a
et b
have kind *
; l'opérateur intégré ->
a kind * -> * -> *
(il prend un type de kind *
à gauche et un à droite et retourne également un type de kind *
). De cela et des règles d'inférence de type, vous pouvez en déduire qu'il a -> b
s'agit d'un type valide avec kind *
, f a
et que ce f b
sont également des types valides avec kind *
, et (a -> b) -> f a -> f b
un type de type valide *
.
En d'autres termes, le compilateur peut "vérifier par nature" l'expression de type (a -> b) -> f a -> f b
pour vérifier qu'elle est valide pour les variables de type du bon type de la même manière qu'elle "vérifie le type" sqrt (a**2 + b**2)
pour vérifier qu'elle est valide pour les variables du bon type.
La raison d'utiliser des termes distincts pour les "types" par rapport aux "types" (c'est-à-dire ne pas parler des "types de types") est principalement pour éviter toute confusion. Les types ci-dessus semblent très différents des types et, au moins au début, semblent se comporter très différemment. (Par exemple, il faut un certain temps pour faire le tour de l'idée que chaque type "normal" a le même type *
et le type ne l' a -> b
est *
pas * -> *
.)
Certains de ces éléments sont également historiques. À mesure que GHC Haskell a évolué, les distinctions entre les valeurs, les types et les types ont commencé à s'estomper. De nos jours, les valeurs peuvent être "promues" en types, et les types et les types sont vraiment la même chose. Ainsi, dans Haskell moderne, les valeurs ont à la fois des types et des types ARE (presque), et les types de types sont juste plus de types.
@ user21820 a demandé des explications supplémentaires sur "les types et les types sont vraiment la même chose". Pour être un peu plus clair, dans GHC Haskell moderne (depuis la version 8.0.1, je pense), les types et les types sont traités uniformément dans la plupart du code du compilateur. Le compilateur fait un certain effort dans les messages d'erreur pour faire la distinction entre "types" et "sortes", selon qu'il se plaint respectivement du type d'une valeur ou du type d'un type.
De plus, si aucune extension n'est activée, elles se distinguent facilement dans le langage de surface. Par exemple, les types (de valeurs) ont une représentation dans la syntaxe (par exemple, dans les signatures de type), mais les types (de types) sont - je pense - complètement implicites, et il n'y a pas de syntaxe explicite où ils apparaissent.
Mais, si vous activez les extensions appropriées, la distinction entre types et types disparaît largement. Par exemple:
{-# LANGUAGE GADTs, TypeInType #-}
data Foo where
Bar :: Bool -> * -> Foo
Ici, Bar
est (à la fois une valeur et) un type. En tant que type, son type est Bool -> * -> Foo
, qui est une fonction de niveau type qui prend un type de type Bool
(qui est un type, mais aussi un type) et un type de type *
et produit un type de type Foo
. Alors:
type MyBar = Bar True Int
vérifie correctement.
Comme l'explique @AndrejBauer dans sa réponse, cette incapacité à distinguer les types et les types n'est pas sûre - avoir un type / type *
dont le type / type est lui-même (ce qui est le cas dans Haskell moderne) conduit à des paradoxes. Cependant, le système de type de Haskell est déjà plein de paradoxes en raison de la non-terminaison, il n'est donc pas considéré comme un gros problème.