Quelle est la différence entre un type et un type?

26

J'apprends la langue de programmation Haskell et j'essaie de comprendre ce qu'est la différence entre a typeet a kind.

Si je comprends bien, a kind is a type of type. Par exemple, a ford is a type of caret 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 vehiclemoment en même temps a car is a **kind** of vehicle. C'est à dire les termes typeet kindsont interchangeables.

Quelqu'un pourrait-il nous éclairer là-dessus?

Thomas cook
la source
6
Je viens de revenir du post sur Stack Overflow qui a conduit à cette discussion. Je ne suis pas sûr d'être qualifié pour répondre en détail - mais vous êtes certainement trop littéral sur les termes "type" et "kind", en essayant de les relier à leur signification en anglais (où en effet ils sont synonymes ). Vous devez les traiter comme des termes techniques. "Type" est bien compris par tous les programmeurs, je suppose, parce que le concept est vital pour toutes les langues, même les plus faiblement typées comme Javascript, "Kind" est un terme technique utilisé dans Haskell pour le "type d'un type". C'est vraiment tout ce qu'il y a à faire.
Robin Zigmond
2
@RobinZigmond: vous avez raison en ce sens que ce sont des termes techniques, mais ils sont plus largement utilisés que dans Haskell. Peut-être un lien retour vers la discussion Stack Overflow qui a engendré cette question?
Andrej Bauer
@AndrejBauer Je n'ai jamais dit qu'ils n'étaient pas utilisés en dehors de Haskell, certainement "type" est utilisé dans pratiquement toutes les langues, comme je l'ai dit. Je n'ai jamais rencontré de "genre" en dehors de Haskell, mais alors Haskell est le seul langage fonctionnel que je connaisse, et j'ai pris soin de ne pas dire que le terme n'est pas utilisé ailleurs, juste qu'il est utilisé de cette façon dans Haskell. (Et le lien, comme vous le demandez, est ici )
Robin Zigmond
Les langages de la famille ML ont également des types, par exemple ML standard et OCaml. Ils ne sont pas explicitement exposés par ce nom, je pense. Ils se manifestent sous forme de signatures et leurs éléments sont appelés structures .
Andrej Bauer
1
Une analogie anglaise plus précise est que Ford est un type de voiture et que la voiture est un type de véhicules, mais les types de voitures et les types de véhicules sont du même type: les noms. Alors que le rouge est un type de couleur de voiture et RPM est un type de métriques de performance de voiture et les deux sont du même type: adjectifs.
slebetman

Réponses:

32

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 aet bseront toujours des valeurs de type Doubleet non, disons Stringet Boolrespectivement. 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 best - 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, aet b), et « application » d'une expression de type à un autre (par exemple, f aest fappliqué à 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 -> adoit ê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 Boolsont é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 -> aressemblent 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 Maybeest 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 Doubleou 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 aa genre * (le genre de types de béton), le résultat de l'expression de type a -> asera également avoir type * ( à savoir le genre de types de béton).

Alors, quel genre de genre est Maybe? Eh bien, Maybepeut être appliqué à un type de béton pour donner un autre type de béton. Cela Mayberessemble 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 Maybeune sorte de signature * -> *. GHCi accepte:

> :kind Maybe
Maybe :: * -> *

Revenir à:

fmap :: Functor f => (a -> b) -> f a -> f b

Dans ce type de signature, variable fa kind * -> *et variables aet bhave 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 -> bs'agit d'un type valide avec kind *, f aet que ce f bsont également des types valides avec kind *, et (a -> b) -> f a -> f bun type de type valide *.

En d'autres termes, le compilateur peut "vérifier par nature" l'expression de type (a -> b) -> f a -> f bpour 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 -> best *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, Barest (à 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.

KA Buhr
la source
Si "les types et les types sont vraiment la même chose", alors le type de typeest juste typelui - même, et il n'y aurait aucun besoin de kind. Alors, quelle est précisément la distinction?
user21820
1
@ user21820, j'ai ajouté une note à la fin qui pourrait résoudre ce problème. Réponse courte: il n'y a pas vraiment de distinction dans le GHC Haskell moderne .
KA Buhr
1
C'est une excellente réponse - merci beaucoup pour le partage. Il est bien écrit et introduit progressivement les concepts - comme quelqu'un qui n'a pas écrit Haskell depuis quelques années, c'est très apprécié!
ultrafez
@KABuhr: Merci pour ce petit plus!
user21820
19

type:kjen=set:cluness.

  • Bool est un type
  • Type est une sorte car ses éléments sont des types
  • Bool -> Int est un type
  • Bool -> Type est une sorte car ses éléments sont des fonctions qui renvoient des types
  • Bool * Int est un type
  • Bool * Type est une sorte parce que ses éléments sont des paires avec un composant d'un type

U0U1U2U0BoolNunetNunetNunetU1U0BoolU0U0U0Un+1UnUn×

U0U1U0U1U0**U_1

Andrej Bauer
la source
2
Je ne pense pas que (GHC) Haskell ait un concept d'univers. Type :: Typeest un axiome. La distinction entre "type" et "genre" est entièrement dans le langage humain, dans ce cas. Truea un type, Boolet Boola un type Type, qui lui-même a un type Type. 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.
HTNW
1
La distinction n'est pas seulement le «langage humain», c'est une distinction formelle dans le système de type sous-jacent. Il est tout à fait possible d'avoir les deux Type :: Typeet une distinction entre les types et les types. De plus, quel élément de code démontre Type :: Typedans Haskell?
Andrej Bauer
1
Je dois également dire que *Haskell est un univers en quelque sorte. Ils n'appellent pas ça comme ça.
Andrej Bauer
3
@AndrejBauer Typede Data.Kindset *devrait être des synonymes. Initialement, nous n'avions *qu'une primitive, alors que de nos jours, elle est définie en interne comme GHC.Types.Typedans le module interne GHC.Types, à son tour définie comme type Type = TYPE LiftedRep. Je pense que TYPEc'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.
chi
1
Je vais essayer de résumer. Si vest une valeur, il a un type: v :: T. Si Test un type, il a un type: T :: K. Le type de type est appelé son type. Les types qui ressemblent TYPE reppeuvent être appelés sortes, bien que le mot soit rare. Iff T :: TYPE repest Tautorisé à apparaître sur le RHS d'un ::. Le mot «genre» a des nuances: Ken T :: Kest une sorte, mais pas en v :: K, même si c'est la même chose K. Nous pourrions définir " Kest 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".
HTNW
5

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. 42est, aux fins de cette explication, un Int; 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 de Int.)

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. IntA en quelque sorte *, et aucun autre genre. MaybeA en quelque sorte * -> *, et pas d' autres types Mais l'intuition doit encore tenir. 42Est un Int, et vous pouvez faire les Intchoses y avec elle comme l'ajout et la soustraction. Intlui-même n'est pas un Int; il n'y a pas de nombre tel que Int + Int. Vous pouvez entendre de manière informelle des gens dire que Intc'est un Num, ce qui signifie qu'il existe une instance de la Numclasse de type pour le type Int- ce n'est pas la même chose comme disant qui Inta genre Num . Inta 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 Maybea du genre * -> *dans Haskell, nous voulons dire qu'il est absurde (formellement; informellement, nous le faisons tout le temps) de parler d'avoir "un Maybe". Au lieu de cela, nous pouvons avoir un Maybe Intou un Maybe 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.

user11228628
la source
1
Je ne dis pas que votre analogie est fausse, mais je pense que cela peut prêter à confusion. Dijkstra a quelques mots sur les analogies. Google "Sur la cruauté de vraiment enseigner l'informatique".
Rafael Castro
Je veux dire, il y a des analogies avec les voitures, puis il y a des analogies avec les voitures. Je ne pense pas que mettre en évidence la structure de type implicite dans un langage naturel (ce qui, je l'admets, je me suis étiré dans la seconde moitié) comme moyen d'expliquer un système de type formel soit le même type d'enseignement par analogie que de parler de ce qu'un programme «veut» faire.
user11228628
1

Si je comprends bien, une sorte est un type de type.

C'est vrai - alors explorons ce que cela signifie. Intou Textsont des types concrets, mais Maybe aest un type abstrait . Il ne deviendra pas un type concret tant que vous n'aurez pas décidé quelle valeur spécifique vous voulez apour une variable particulière (ou valeur / expression / autre), par exemple Maybe Text.

Nous disons que Maybe ac'est un constructeur de type car c'est comme une fonction qui prend un seul type concret (par exemple Text) et renvoie un type concret ( Maybe Textdans 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, il Map k vfaut prendre deux types de béton (par exemple Intet Text) avant de pouvoir construire un type de béton ( Map Int Text).

Ainsi, les constructeurs Maybe aet List atypes 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 et Maybeet Listavons 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

Mark Lassau
la source
Ce n'est pas correct. Dans Haskell, on distingue entre Maybe a, synonyme de forall a. Maybe a, un type polymorphique de type *, et Maybe, un type monomorphe de genre * -> *.
b0fh