Was ist der Unterschied zwischen einem Typ und einer Art?

26

Ich lerne die Programmiersprache Haskell und versuche, mich mit dem Unterschied zwischen a typeund a auseinanderzusetzen kind.

Wie ich es verstehe, a kind is a type of type. Zum Beispiel a ford is a type of carund a car is a kind of vehicle.

Ist dies eine gute Möglichkeit, darüber nachzudenken?

Denn so wie mein Gehirn gerade verdrahtet ist, a ford is a **type** of car, aber auch eine car is a **type** of vehicleWeile gleichzeitig a car is a **kind** of vehicle. Dh die Begriffe typeund kindsind austauschbar.

Könnte jemand etwas Licht ins Dunkel bringen?

Thomas Cook
quelle
6
Ich bin gerade von dem Beitrag über Stack Overflow hierher gekommen, der zu dieser Diskussion geführt hat. Ich bin mir nicht sicher, ob ich in der Lage bin, ausführlich zu antworten - aber Sie sind definitiv viel zu wörtlich in Bezug auf die Begriffe "Typ" und "Art", wenn Sie versuchen, sie mit ihrer Bedeutung auf Englisch in Verbindung zu bringen (wobei es sich in der Tat um Synonyme handelt) ). Sie sollten sie als Fachbegriffe behandeln. "Type" ist für alle Programmierer gut verständlich, da das Konzept für jede Sprache von entscheidender Bedeutung ist, auch für schwach typisierte wie Javascript. "Kind" ist in Haskell ein technischer Begriff für den "Typ eines Typs". Das ist wirklich alles was dazu gehört.
Robin Zigmond
2
@RobinZigmond: Sie haben Recht, dass dies technische Begriffe sind, aber sie werden weit verbreiteter als nur in Haskell verwendet. Vielleicht ein Backlink zur Stack Overflow-Diskussion, aus der diese Frage hervorgegangen ist?
Andrej Bauer
@AndrejBauer Ich habe nie gesagt, dass sie nicht außerhalb von Haskell verwendet werden, sicherlich wird "Typ" in im Wesentlichen jeder Sprache verwendet, wie ich sagte. Ich bin außerhalb von Haskell noch nie auf "Art" gestoßen, aber dann ist Haskell die einzige funktionale Sprache, die ich überhaupt kenne, und ich habe darauf geachtet, nicht zu sagen, dass der Begriff anderswo nicht verwendet wird, nur dass er in dieser Form verwendet wird Haskell. (Und der Link, wie Sie verlangen, ist hier )
Robin Zigmond
ML-Familiensprachen haben auch Arten, zum Beispiel Standard ML und OCaml. Sie sind nicht explizit durch diesen Namen entlarvt, denke ich. Sie manifestieren sich als Signaturen , und ihre Elemente werden als Strukturen bezeichnet .
Andrej Bauer
1
Eine genauere englische Analogie ist, dass Ford eine Art Auto und Auto eine Art Fahrzeug ist, aber sowohl Fahrzeugtypen als auch Fahrzeugtypen sind von der gleichen Art: Substantive. Während Rot eine Art von Autofarbe ist und die Drehzahl eine Art von Kennzahlen für die Fahrzeugleistung ist, haben beide dieselbe Art: Adjektive.
Slebetman

Antworten:

32

Hier haben "Werte", "Typen" und "Arten" formale Bedeutungen. Wenn Sie also die gebräuchliche englische Sprache oder Analogien zur Klassifizierung von Automobilen berücksichtigen, werden Sie nur so weit kommen.

Meine Antwort bezieht sich auf die formale Bedeutung dieser Begriffe im Zusammenhang mit Haskell; Diese Bedeutungen basieren auf den Bedeutungen, die in der mathematischen / CS "Typentheorie" verwendet werden (obwohl sie nicht wirklich identisch sind). Das wird also keine sehr gute "Computerwissenschaft" -Antwort sein, aber es sollte eine ziemlich gute Haskell-Antwort sein.

In Haskell (und anderen Sprachen), endet es auf ein Wesen hilfreich zuweisen Typen zu einem Programmausdruck, der die Klasse von beschreiben Werten der Ausdruck erlaubt ist zu haben. Ich gehe davon aus, dass Sie genug Beispiele gesehen haben, um zu verstehen, warum es nützlich wäre, das im Ausdruck zu wissensqrt (a**2 + b**2) , die Variablen aund bwerden immer Werte vom Typ sein Doubleund nicht, sagen, Stringund Booljeweils. Grundsätzlich hilft uns das Haben von Typen beim Schreiben von Ausdrücken / Programmen, die über einen weiten Bereich von Werten korrekt funktionieren .

Vielleicht haben Sie nicht bemerkt, dass Haskell-Typen wie die in Typensignaturen vorkommenden:

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

sind eigentlich selbst in einer Typ-Level-Haskell-Subsprache geschrieben. Der Programmtext Functor f => (a -> b) -> f a -> f bist - im wahrsten Sinne des Wortes - ein typischer Ausdruck in diesem Subsprache geschrieben. Die Untersprache enthält Operatoren (beispielsweise ->ist eine rechte assoziative Infixoperator in dieser Sprache), Variablen ( zum Beispiel f, a, und b), und „Anwendung“ eines Typ Ausdruck zu einem anderen ( zum Beispiel f awird fangewandt a).

Habe ich erwähnt, wie hilfreich es in vielen Sprachen ist, Programmausdrücken Typen zuzuweisen, um Klassen von Ausdruckswerten zu beschreiben? Nun, in dieser Subsprache auf Typebene werden Ausdrücke als Typen (und nicht als Werte ) ausgewertet, und es ist letztendlich hilfreich, sie zuzuweisen Arten , um die Klassen von Typen zu beschreiben, die sie darstellen dürfen. Grundsätzlich hilft uns das Haben von Arten beim Schreiben von Typausdrücken, die über einen weiten Bereich von Typen korrekt funktionieren .

So Werte sind Typen als Typen zu sind Arten und Typen helfen uns schreiben Wert -Niveau Programme während Arten helfen uns schreiben Art -Niveau Programme.

Wie sehen diese Arten aus? Nun, betrachten Sie die Typensignatur:

id :: a -> a

Wenn der Typausdruck a -> agültig sein soll, welche Arten von Typen sollten wir die Variablen zulassen a? Nun, die Typausdrücke:

Int -> Int
Bool -> Bool

siehst gültig aus, also die typen Int und Boolsind offensichtlich von der richtigen art . Aber auch kompliziertere Typen wie:

[Double] -> [Double]
Maybe [(Double,Int)] -> Maybe [(Double,Int)]

siehst gültig aus. In der Tat, da wir in der Lage sein sollten, idFunktionen aufzurufen , sogar:

(a -> a) -> (a -> a)

sieht gut aus. Also Int, Bool, [Double], Maybe [(Double,Int)], und a -> aalle sehen aus wie Typen der richtigen Art .

Mit anderen Worten, es scheint nur eine Art zu geben. Nennen wir es *wie einen Unix-Platzhalter und jeden Typ hat die gleiche Art * , Ende der Geschichte.

Recht?

Nicht ganz. Es stellt sich heraus, dass Maybeein Typ-Ausdruck für sich genommen genauso gültig ist wie Maybe Int(in etwa der gleiche Weg sqrt, für sich genommen, ist ein Wert-Ausdruck genauso gültig wie sqrt 25). jedoch folgende Typausdruck ist jedoch ungültig:

Maybe -> Maybe

Denn während Maybeein Ausdruck des Typs ist, ist es nicht die repräsentiert Art von Typ , die Werte aufweisen können. Also, das ist , wie wir definieren sollte *- es ist die Art von Typen , die Werte aufweisen; es enthält "vollständige" Typen wie DoubleoderMaybe [(Double,Int)] schließt jedoch unvollständige, wertlose Typen wie aus Either String. Der Einfachheit halber werde ich diese vollständigen Arten von Arten *"konkrete Arten" nennen, obwohl diese Terminologie nicht universell ist, und "konkrete Arten" können etwas ganz anderes bedeuten als beispielsweise ein C ++ - Programmierer.

Nun, in der Art Ausdruck a -> a, solange Art ahat Art * (die Art von Beton - Typen), das Ergebnis der Art Ausdrucks a -> awird auch haben Art * (dh die Art von Beton - Typen).

Also, welche Art von Typ ist Maybe? Nun, Maybekann auf einen Betontyp angewendet werden, um einen anderen Betontyp zu ergeben. Also, Maybesieht aus wie ein wenig wie eine Typ-Level - Funktion , die eine nimmt Art von Art * und gibt eine Art von Art * . Wenn wir einen Wert Level - Funktion haben , die einen nahm Wert von Typ Int und einen zurück Wert von Typ Int , würden wir ihm eine geben Art Unterschrift Int -> Int, so analog sollten wir geben Maybeeine Art Unterschrift * -> *. GHCi stimmt zu:

> :kind Maybe
Maybe :: * -> *

Zurück gehen zu:

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

In dieser Typensignatur hat Variable fArt * -> *und Variablen aund bArt *; Der eingebaute Operator ->hat Art * -> * -> *(er nimmt eine Art *auf der linken und eine auf der rechten Seite und gibt auch eine Art zurück *). Daraus und aus den Rückschlüssen auf die Art können Sie ableiten, dass a -> bes sich um einen gültigen Typ mit Art *handelt f aund dass f bes sich auch um gültige Typen mit Art *handelt und dass es sich um einen gültigen Typ (a -> b) -> f a -> f bhandelt *.

Mit anderen Worten, der Compiler kann den Typausdruck " (a -> b) -> f a -> f büberprüfen" sqrt (a**2 + b**2), um zu überprüfen, ob er für Typvariablen des richtigen Typs gültig ist, ebenso wie er "überprüft" , ob er für Variablen des richtigen Typs gültig ist.

Der Grund für die Verwendung separater Ausdrücke für "Typen" gegenüber "Arten" (dh nicht über die "Typen von Typen" zu sprechen) ist meist nur, um Verwirrung zu vermeiden. Die Arten oben Blick sehr verschieden von Typen und, zumindest auf dem ersten, scheinen ganz anders zu verhalten. (Zum Beispiel dauert es einige Zeit , den Kopf wickeln sich um die Idee , dass jeder „normalen“ hat die gleiche Art *und die Art a -> bist *nicht * -> *.)

Einiges davon ist auch historisch. Mit der Entwicklung von GHC Haskell verschwimmen die Unterschiede zwischen Werten, Typen und Arten. In diesen Tagen können Werte in Typen "befördert" werden, und Typen und Arten sind wirklich dasselbe. Daher haben in modernen Haskell-Werten sowohl Typen als auch ARE- Typen (fast), und die Typenarten sind nur mehr Typen.

@ user21820 fragte nach einer zusätzlichen Erklärung für "Typen und Arten sind wirklich dasselbe". Um es ein bisschen klarer zu machen: In modernen GHC Haskell (seit Version 8.0.1, glaube ich) werden Typen und Arten im Compiler-Code weitgehend einheitlich behandelt . Der Compiler unternimmt einige Anstrengungen bei Fehlermeldungen, um zwischen "Typen" und "Arten" zu unterscheiden, je nachdem, ob er sich über den Typ eines Werts oder den Typ eines Typs beschwert.

Auch wenn keine Erweiterungen aktiviert sind, sind sie leicht zu unterscheiden in der Oberflächensprache. Zum Beispiel haben Typen (von Werten) eine Darstellung in der Syntax (z. B. in Typensignaturen), aber Arten (von Typen) sind - glaube ich - völlig implizit und es gibt keine explizite Syntax, in der sie erscheinen.

Wenn Sie jedoch die entsprechenden Erweiterungen aktivieren, verschwindet die Unterscheidung zwischen Typen und Arten weitgehend. Beispielsweise:

{-# LANGUAGE GADTs, TypeInType #-}
data Foo where
  Bar :: Bool -> * -> Foo

Hier Barist (sowohl ein Wert als auch) ein Typ. Als Typ ist seine Art Bool -> * -> Fooeine Funktion auf Typebene, die einen Typ Bool(der ein Typ, aber auch eine Art ist) und einen Typ einer Art annimmt und einen Typ einer Art *erzeugt Foo. So:

type MyBar = Bar True Int

korrekt überprüft.

Wie @AndrejBauer in seiner Antwort erklärt, ist diese Unterscheidung zwischen Typen und Arten unsicher - ein Typ / eine Art, *deren Typ / Art selbst ist (was im modernen Haskell der Fall ist), führt zu Paradoxen. Das Typensystem von Haskell ist jedoch aufgrund der Nichtbeendigung bereits voller Paradoxien, weshalb es nicht als große Sache angesehen wird.

KA Buhr
quelle
Wenn "Typen und Arten wirklich dasselbe sind", dann ist der Typ von typenur sich typeselbst, und es würde überhaupt keine Notwendigkeit dafür geben kind. Was genau ist der Unterschied?
user21820
1
@ user21820, Ich habe am Ende einen Hinweis hinzugefügt, der dies möglicherweise anspricht. Kurze Antwort: Im modernen GHC Haskell gibt es keinen wirklichen Unterschied .
KA Buhr
1
Dies ist eine großartige Antwort - vielen Dank für das Teilen. Es ist gut geschrieben und führt Konzepte schrittweise ein - als jemand, der Haskell seit einigen Jahren nicht mehr geschrieben hat, wird dies sehr geschätzt!
Ultrafez
@KABuhr: Danke für das zusätzliche bisschen!
user21820
19

type:kichnd=set:cleinss.

  • Bool ist ein Typ
  • Type ist eine Art, weil ihre Elemente Typen sind
  • Bool -> Int ist ein Typ
  • Bool -> Type ist eine Art, weil ihre Elemente Funktionen sind, die Typen zurückgeben
  • Bool * Int ist ein Typ
  • Bool * Type ist eine Art, weil ihre Elemente Paare mit einer Komponente eines Typs sind

U0U1U2U0BOOlNeintNeintNeintU1U0BOOlU0U0U0Un+1UnUn×

U0U1U0U1U0* . Somit *handelt es sich um ein Element U_1, das Haskell nicht explizit benennt.

Andrej Bauer
quelle
2
Ich glaube nicht, dass (GHC) Haskell ein Konzept von Universen hat. Type :: Typeist ein Axiom. Die Unterscheidung zwischen "Typ" und "Art" liegt in diesem Fall ausschließlich in der menschlichen Sprache. Truehat einen Typ, Boolund Boolhat einen Typ Type, der selbst Typ hat Type. Manchmal nennen wir einen Typ eine Art, um zu betonen, dass es sich um den Typ einer Entität auf Typebene handelt, in Haskell handelt es sich jedoch immer noch um einen Typ. In einem System , wo Universen tatsächlich existieren, wie Coq, dann „Typ“ kann sich auf ein Universum beziehen und „Art“ zu einem anderen, aber dann in der Regel wollen wir unendlich viele Universen.
HTNW
1
Die Unterscheidung ist nicht nur "menschliche Sprache", sondern eine formale Unterscheidung im zugrunde liegenden Typensystem. Es ist durchaus möglich, beides zu haben Type :: Typeund zwischen Typen und Arten zu unterscheiden. Welcher Code wird auch Type :: Typein Haskell demonstriert ?
Andrej Bauer
1
Ich sollte auch sagen, dass es *in Haskell eine Art Universum gibt. Sie nennen es einfach nicht so.
Andrej Bauer
3
@AndrejBauer Typevon Data.Kindsund *sollten Synonyme sein. Anfangs hatten wir nur *als Primitiv, während heutzutage das intern wie GHC.Types.Typeim internen Modul GHC.Typesdefiniert ist, wiederum definiert als type Type = TYPE LiftedRep. Ich denke, es TYPEist das eigentliche Primitiv, das eine Reihe von Arten bereitstellt (angehobene Typen, unboxed Typen, ...). Der größte Teil der "uneleganten" Komplexität besteht darin, einige Optimierungen auf niedriger Ebene zu unterstützen, und dies nicht aus typentheoretischen Gründen.
Chi
1
Ich werde versuchen zusammenzufassen. Wenn vein Wert ist, dann hat es einen Typ: v :: T. Wenn Tein Typ ist, dann hat es einen Typ: T :: K. Die Art der Art heißt Art. Typen, die aussehen, TYPE repkönnen als Sorten bezeichnet werden, obwohl das Wort ungewöhnlich ist. Iff T :: TYPE repwird Tauf der rechten Seite von a erscheinen dürfen ::. Das Wort "Art" hat eine Nuance: KIn T :: Kist eine Art, aber nicht in v :: K, obwohl es das gleiche ist K. Wir könnten definieren " Kist eine Art, wenn ihre Art eine Art ist" oder "Arten sind auf der rechten Seite von ::", aber das erfasst die Verwendung nicht richtig. Daher meine "menschliche Auszeichnung" Position.
HTNW
5

EIN Wert ist wie der spezifische rote 2011 Ford Mustang mit 19.206 Meilen darauf, den Sie in Ihrer Fahrstraße sitzen haben.

Informell könnte dieser spezifische Wert viele haben Arten haben : Es ist ein Mustang und es ist ein Ford und es ist ein Auto und es ist ein Fahrzeug, neben vielen anderen Arten, die man erfinden könnte (die Art von "Dingen") Ihnen gehören ", oder die Art der" Dinge, die rot sind ", oder ...).

(In Haskell haben Werte in erster Näherung (GADTs unterbrechen diese Eigenschaft, und die Magie um Zahlenliterale und die OverloadedStrings-Erweiterung verdecken sie ein wenig) einen Haupttyp anstelle der Fülle informeller "Typen", die Sie angeben können. ' stach. 42 ist im Sinne dieser Erklärung ein Int; es gibt in Haskell keinen Typ für "Zahlen" oder "sogar ganze Zahlen" - oder Sie könnten einen machen, aber es wäre ein disjunkter Typ vonInt .)

Jetzt kann "Mustang" ein Untertyp von "Auto" sein - jeder Wert, der ein Mustang ist, ist auch ein Auto. Aber der Typ - oder, um Haskells Terminologie zu verwenden, ist die Art von "Mustang" nicht "Auto". "Mustang" der Typ ist keine Sache, die Sie in Ihrer Einfahrt parken oder in der Sie herumfahren können. "Mustang" ist ein Substantiv oder eine Kategorie oder nur ein Typ. Das sind informell die Arten von "Mustang".

(Wiederum erkennt Haskell nur eine Art für jede Sache auf Typebene. Also Inthat Art *und keine anderen Arten. MaybeHat Art * -> *und keine anderen Arten. Aber die Intuition sollte immer noch halten: 42ist eine Int, und Sie können IntDinge damit tun . wie Addieren und Subtrahieren Intselbst ist nicht Int, es als eine solche Zahl ist Int + IntSie können formlos Leute sagen hören , dass. Inteine ist Num, durch die sie bedeuten , dass ein dort ist beispielsweise der NumTyp - Klasse für den Typ Int-Das ist nicht das gleiche als zu sagen, dass Inthat Art Num . Inthat Art "Typ", die in Haskell geschrieben ist *.)

Ist also nicht jeder informelle "Typ" nur ein Substantiv oder eine Kategorie? Haben alle Typen die gleiche Art? Warum überhaupt über Arten reden, wenn sie so langweilig sind?

Dies ist der Punkt, an dem die englische Analogie ein wenig steiniger wird, aber bedenken Sie: Stellen Sie sich vor, dass das Wort "Eigentümer" auf Englisch für sich genommen keinen Sinn ergibt, ohne dass eine Beschreibung des Eigentums vorliegt. Stellen Sie sich vor, dass jemand, der Sie "Eigentümer" nennt, für Sie überhaupt keinen Sinn ergibt. Aber wenn dich jemand einen "Autobesitzer" nennt, kannst du verstehen, was er meint.

"Besitzer" hat nicht die gleiche Art wie "Auto", weil man in dieser erfundenen Version des Englischen nicht über ein Auto sprechen kann, aber über einen Besitzer. Man kann nur von einem "Autobesitzer" sprechen. "Eigentümer" erstellt nur dann ein "Nomen", wenn es auf etwas angewendet wird, das bereits ein "Nomen" hat, wie "Auto". Wir würden sagen, dass die Art von "Eigentümer" "Nomen -> Nomen" ist. "Owner" ist wie eine Funktion, die ein Nomen annimmt und daraus ein anderes Nomen erzeugt; Aber es ist kein Substantiv.

Beachten Sie, dass "Autobesitzer" kein Untertyp von "Auto" ist! Es ist keine Funktion, die Autos akzeptiert oder zurückgibt! Es ist nur eine völlig andere Art von "Auto". Es beschreibt Werte mit zwei Armen und zwei Beinen, die zu einem bestimmten Zeitpunkt einen bestimmten Geldbetrag hatten und dieses Geld zu einem Händler brachten. Es werden keine Werte beschrieben, die über vier Räder und eine Lackierung verfügen. Beachten Sie auch, dass "Autobesitzer" und "Hundebesitzer" unterschiedliche Typen sind und Dinge, die Sie mit einem machen möchten, möglicherweise nicht auf den anderen zutreffen.

(Ebenso, wenn wir in Haskell sagen, dass das Maybeeine Art hat * -> *, ist es unsinnig (formal; informell tun wir es die ganze Zeit), über "ein Maybe" zu sprechen . Stattdessen können wir ein Maybe Intoder ein haben Maybe String, da dies Dinge von sind Art *.)

Es geht also darum, überhaupt über Arten zu sprechen, damit wir unsere Argumentation in Bezug auf Wörter wie "Eigentümer" formalisieren und durchsetzen können, dass wir immer nur Werte von Typen verwenden, die "vollständig konstruiert" und nicht unsinnig sind.

user11228628
quelle
1
Ich sage nicht, dass Ihre Analogie falsch ist, aber ich denke, es kann Verwirrung stiften. Dijkstra hat einige Worte zu Analogien. Google "Über die Grausamkeit, Informatik wirklich zu unterrichten".
Rafael Castro
Ich meine, es gibt Auto-Analogien und dann gibt es Auto-Analogien. Ich denke nicht, dass das Hervorheben der impliziten Typstruktur in einer natürlichen Sprache (die ich zugegebenermaßen in der zweiten Hälfte ausgedehnt habe) als eine Art der Erklärung eines formalen Typensystems die gleiche Art des Lehrens durch Analogie ist wie das Sprechen darüber was ein Programm "will".
user11228628
1

So wie ich es verstehe, ist eine Art eine Art von Typ.

Das ist richtig - also lasst uns untersuchen, was das bedeutet. Intoder Textsind konkrete Typen, aber Maybe aein abstrakter Typ. Es wird erst dann zu einem konkreten Typ, wenn Sie entscheiden, welchen bestimmten Wert Sie afür eine bestimmte Variable (oder Wert / Ausdruck / was auch immer) möchten, z Maybe Text.

Wir sagen, das Maybe aist ein Typkonstruktor, weil es wie eine Funktion ist, die einen einzelnen konkreten Typ annimmt (z. B. Text) und einen konkreten Typ zurückgibt ( Maybe Textin diesem Fall). Andere Typkonstruktoren benötigen jedoch möglicherweise noch mehr "Eingabe-Parameter", bevor sie einen konkreten Typ zurückgeben. ZB Map k vmüssen zwei konkrete Typen (z. B. Intund Text) verwendet werden, bevor ein konkreter Typ ( Map Int Text) erstellt werden kann.

Die Konstruktoren Maybe aund List atype haben also dieselbe "Signatur", die wir bezeichnen * -> *(ähnlich wie die Haskell-Funktionssignatur), da sie, wenn Sie ihnen einen konkreten Typ geben, einen konkreten Typ ausspucken. Wir nennen dies die "Art" des Typs Maybeund Listhaben die gleiche Art.

Die konkreten Typen haben eine Art *, und unser Kartenbeispiel ist eine Art, * -> * -> *da zwei konkrete Typen als Eingabe verwendet werden, bevor ein konkreter Typ ausgegeben werden kann.

Sie können sehen , es ist meist nur über die Anzahl der „Parameter“ , dass wir in die Art Konstruktor übergeben - aber erkennen , dass wir auch innerhalb Typkonstruktoren verschachtelt Typkonstruktoren erhalten können, so dass wir mit einer Art können am Ende , dass sieht aus wie * -> (* -> *) -> *zum Beispiel .

Wenn Sie ein Scala / Java-Entwickler sind, finden Sie diese Erklärung möglicherweise auch hilfreich: https://www.atlassian.com/blog/archives/scala-types-of-a-higher-kind

Mark Lassau
quelle
Das ist nicht richtig. In Haskell unterscheiden wir zwischen Maybe aeinem Synonym für forall a. Maybe aeinen polymorphen Typ *und Maybeeinem monomorphen Typ * -> *.
5.