J'apprends la langue de programmation Haskell et j'essaie de comprendre ce qu'est la différence entre a type
et a kind
.
Si je comprends bien, a kind is a type of type
. Par exemple, a ford is a type of car
et a car is a kind of vehicle
.
Est-ce une bonne façon d'y penser?
Parce que, la façon dont mon cerveau est actuellement câblé, a ford is a **type** of car
, mais aussi un car is a **type** of vehicle
moment en même temps a car is a **kind** of vehicle
. C'est à dire les termes type
et kind
sont interchangeables.
Quelqu'un pourrait-il nous éclairer là-dessus?
type-theory
Thomas cook
la source
la source
Réponses:
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 variablesa
etb
seront toujours des valeurs de typeDouble
et non, disonsString
etBool
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:
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 exemplef
,a
etb
), et « application » d'une expression de type à un autre (par exemple,f a
estf
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:
Si l'expression de type
a -> a
doit être valide, quel type de types devrions-nous autorisera
à être variable ? Eh bien, les expressions de type:semblent valides, donc les types
Int
etBool
sont évidemment du bon type . Mais des types encore plus compliqués comme:semble valide. En fait, puisque nous devons pouvoir faire appel
id
à des fonctions, même:semble bien. Ainsi,
Int
,Bool
,[Double]
,Maybe [(Double,Int)]
et ilsa -> 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 queMaybe Int
(de la même manièresqrt
, tout seul, est tout aussi valide une expression de valeur quesqrt 25
). Cependant , l'expression de type suivante n'est pas valide: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" commeDouble
ouMaybe [(Double,Int)]
mais exclut les types incomplets sans valeur commeEither 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 typea
a genre*
(le genre de types de béton), le résultat de l'expression de typea -> 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. CelaMaybe
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 typeInt
et renvoyait une valeur de typeInt
, nous lui donnerions une signature de typeInt -> Int
, donc par analogie, nous devrions donnerMaybe
une sorte de signature* -> *
. GHCi accepte:Revenir à:
Dans ce type de signature, variable
f
a kind* -> *
et variablesa
etb
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'ila -> b
s'agit d'un type valide avec kind*
,f a
et que cef 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:
Ici,
Bar
est (à la fois une valeur et) un type. En tant que type, son type estBool -> * -> Foo
, qui est une fonction de niveau type qui prend un type de typeBool
(qui est un type, mais aussi un type) et un type de type*
et produit un type de typeFoo
. Alors: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.la source
type
est justetype
lui - même, et il n'y aurait aucun besoin dekind
. Alors, quelle est précisément la distinction?Bool
est un typeType
est une sorte car ses éléments sont des typesBool -> Int
est un typeBool -> Type
est une sorte car ses éléments sont des fonctions qui renvoient des typesBool * Int
est un typeBool * Type
est une sorte parce que ses éléments sont des paires avec un composant d'un type*
*
U_1
la source
Type :: Type
est un axiome. La distinction entre "type" et "genre" est entièrement dans le langage humain, dans ce cas.True
a un type,Bool
etBool
a un typeType
, qui lui-même a un typeType
. Parfois, nous appelons un type un type, pour souligner qu'il s'agit du type d'une entité de niveau type, mais, dans Haskell, ce n'est encore qu'un type. Dans un système où les univers existent réellement, comme Coq, alors "type" peut se référer à un univers et "genre" à un autre, mais alors nous voulons généralement une infinité d'univers.Type :: Type
et une distinction entre les types et les types. De plus, quel élément de code démontreType :: Type
dans Haskell?*
Haskell est un univers en quelque sorte. Ils n'appellent pas ça comme ça.Type
deData.Kinds
et*
devrait être des synonymes. Initialement, nous n'avions*
qu'une primitive, alors que de nos jours, elle est définie en interne commeGHC.Types.Type
dans le module interneGHC.Types
, à son tour définie commetype Type = TYPE LiftedRep
. Je pense queTYPE
c'est la vraie primitive, fournissant une famille de types (types soulevés, types non emballés, ...). La plus grande partie de la complexité «inélégante» consiste à prendre en charge certaines optimisations de bas niveau, et non pour des raisons théoriques de type réelles.v
est une valeur, il a un type:v :: T
. SiT
est un type, il a un type:T :: K
. Le type de type est appelé son type. Les types qui ressemblentTYPE rep
peuvent être appelés sortes, bien que le mot soit rare. IffT :: TYPE rep
estT
autorisé à apparaître sur le RHS d'un::
. Le mot «genre» a des nuances:K
enT :: K
est une sorte, mais pas env :: K
, même si c'est la même choseK
. Nous pourrions définir "K
est une sorte si sa sorte est une sorte" aka "les sortes sont sur le RHS de::
", mais cela ne capture pas correctement l'utilisation. D'où ma position de "distinction humaine".Une valeur est comme la Ford Mustang 2011 rouge spécifique avec 19 206 miles sur elle que vous avez assis dans votre allée.
Cette valeur spécifique, officieusement, pourrait avoir de nombreux types : c'est une Mustang, et c'est une Ford, et c'est une voiture, et c'est un véhicule, parmi beaucoup d'autres types que vous pourriez inventer (le type de "choses" vous appartenant ", ou le type de" choses qui sont rouges ", ou ...).
(Dans Haskell, selon une approximation de premier ordre (les GADT brisent cette propriété, et la magie autour des littéraux numériques et l'extension OverloadedStrings l'obscurcissent un peu)), les valeurs ont un type principal au lieu de la pléthore de "types" informels que vous pouvez donner à votre ' stang.
42
est, aux fins de cette explication, unInt
; il n'y a pas de type dans Haskell pour "nombres" ou "entiers pairs" - ou plutôt, vous pouvez en faire un, mais ce serait un type disjoint deInt
.)Maintenant, "Mustang" peut être un sous - type de "voiture" - chaque valeur qui est une Mustang est aussi une voiture. Mais le type - ou, pour utiliser la terminologie de Haskell, le type de «Mustang» n'est pas «voiture». "Mustang" le type n'est pas une chose que vous pouvez garer dans votre allée ou conduire. "Mustang" est un nom, ou une catégorie, ou juste un type. Ce sont, officieusement, les sortes de "Mustang".
(Encore une fois, Haskell ne reconnaît qu'un seul type pour chaque chose au niveau du type Donc.
Int
A en quelque sorte*
, et aucun autre genre.Maybe
A en quelque sorte* -> *
, et pas d' autres types Mais l'intuition doit encore tenir.42
Est unInt
, et vous pouvez faire lesInt
choses y avec elle comme l'ajout et la soustraction.Int
lui-même n'est pas unInt
; il n'y a pas de nombre tel queInt + Int
. Vous pouvez entendre de manière informelle des gens dire queInt
c'est unNum
, ce qui signifie qu'il existe une instance de laNum
classe de type pour le typeInt
- ce n'est pas la même chose comme disant quiInt
a genreNum
.Int
a genre "type", qui dans Haskell est orthographié*
.)Donc, chaque "type" informel n'est-il pas simplement un nom ou une catégorie? Tous les types ont-ils le même type? Pourquoi parler de sortes si elles sont si ennuyeuses?
C'est là que l'analogie anglaise deviendra un peu difficile, mais soyez indulgent: prétendez que le mot «propriétaire» en anglais n'a aucun sens isolément, sans une description de la propriété. Imaginez que si quelqu'un vous appelait un "propriétaire", cela n'aurait aucun sens pour vous; mais si quelqu'un vous appelait un "propriétaire de voiture", vous pouviez comprendre ce qu'il voulait dire.
"Propriétaire" n'a pas le même genre que "voiture", car vous pouvez parler d'une voiture, mais vous ne pouvez pas parler d'un propriétaire dans cette version inventée de l'anglais. Vous ne pouvez parler que d'un "propriétaire de voiture". "Propriétaire" ne crée quelque chose de type "nom" que lorsqu'il est appliqué à quelque chose qui a déjà un "nom", comme "voiture". Nous dirions que le type de "propriétaire" est "nom -> nom". "Propriétaire" est comme une fonction qui prend un nom et produit à partir de cela un nom différent; mais ce n'est pas un nom lui-même.
Notez que "propriétaire de voiture" n'est pas un sous-type de "voiture"! Ce n'est pas une fonction qui accepte ou retourne des voitures! C'est juste un type complètement différent de "voiture". Il décrit des valeurs avec deux bras et deux jambes qui, à un moment donné, avaient une certaine somme d'argent, et ont pris cet argent chez un concessionnaire. Il ne décrit pas les valeurs qui ont quatre roues et un travail de peinture. Notez également que «propriétaire de voiture» et «propriétaire de chien» sont de types différents, et que les choses que vous voudrez peut-être faire avec l'un peuvent ne pas s'appliquer à l'autre.
(De même, lorsque nous disons que cela
Maybe
a du genre* -> *
dans Haskell, nous voulons dire qu'il est absurde (formellement; informellement, nous le faisons tout le temps) de parler d'avoir "unMaybe
". Au lieu de cela, nous pouvons avoir unMaybe Int
ou unMaybe String
, car ce sont des choses de genre*
.)Donc, tout l'intérêt de parler de types est de sorte que nous puissions formaliser notre raisonnement autour de mots comme "propriétaire" et faire en sorte que nous ne prenions que des valeurs de types qui ont été "entièrement construits" et qui n'ont pas de sens.
la source
C'est vrai - alors explorons ce que cela signifie.
Int
ouText
sont des types concrets, maisMaybe a
est un type abstrait . Il ne deviendra pas un type concret tant que vous n'aurez pas décidé quelle valeur spécifique vous vouleza
pour une variable particulière (ou valeur / expression / autre), par exempleMaybe Text
.Nous disons que
Maybe a
c'est un constructeur de type car c'est comme une fonction qui prend un seul type concret (par exempleText
) et renvoie un type concret (Maybe Text
dans ce cas). Mais d'autres constructeurs de types peuvent prendre encore plus de "paramètres d'entrée" avant de retourner un type concret. Par exemple, ilMap k v
faut prendre deux types de béton (par exempleInt
etText
) avant de pouvoir construire un type de béton (Map Int Text
).Ainsi, les constructeurs
Maybe a
etList a
types ont la même "signature" que nous désignons* -> *
(de la même manière que la signature de la fonction Haskell) parce que si vous leur donnez un type concret, ils cracheront un type concret. Nous appelons cela le "type" du type etMaybe
etList
avons le même type.Les types concrets sont censés avoir kind
*
, et notre exemple Map est kind* -> * -> *
car il prend deux types concrets en entrée avant de pouvoir sortir un type concret.Vous pouvez voir qu'il s'agit principalement du nombre de "paramètres" que nous transmettons au constructeur de type - mais réalisez que nous pouvons également obtenir des constructeurs de type imbriqués dans des constructeurs de type, afin que nous puissions nous retrouver avec un type qui ressemble
* -> (* -> *) -> *
par exemple à .Si vous êtes un développeur Scala / Java, cette explication peut également vous être utile: https://www.atlassian.com/blog/archives/scala-types-of-a-higher-kind
la source
Maybe a
, synonyme deforall a. Maybe a
, un type polymorphique de type*
, etMaybe
, un type monomorphe de genre* -> *
.