Richtige Terminologie in der Typentheorie: Typen, Typkonstruktoren, Arten / Sorten und Werte


14

Bei der Beantwortung einer früheren Frage begann eine kleine Debatte über die korrekte Terminologie für bestimmte Konstrukte. Da ich keine andere Frage (außer diese oder jene , die nicht ganz die richtige ist) gefunden habe, um dies klar anzusprechen, mache ich diese neue.

Die fraglichen Begriffe und ihre Beziehungen sind: Typ, Typkonstruktor, Typparameter, Arten oder Sortierungen und Werte .

Ich habe auch Wikipedia auf Typentheorie überprüft , aber das hat es auch nicht viel geklärt.

Um eine gute Referenzantwort zu haben und mein eigenes Verständnis zu überprüfen:

  • Wie sind diese Dinge richtig definiert?
  • Was ist der Unterschied zwischen all diesen Dingen?
  • Wie hängen sie zusammen?

Antworten:


13

Okay, lass uns eins nach dem anderen gehen.

Werte

Werte sind die konkreten Daten, die Programme auswerten und jonglieren. Nichts Besonderes, vielleicht einige Beispiele

  • 1
  • true
  • "fizz buzz foo bar"

Typen

Eine schöne Beschreibung für einen Typ ist "ein Klassifikator für einen Wert". Ein Typ ist eine kleine Information darüber, was dieser Wert zur Laufzeit sein wird, wird aber zur Kompilierungszeit angezeigt.

Wenn Sie mir zum Beispiel sagen, dass dies e : boolzur Kompilierungszeit der Fall ist und ich weiß, dass dies eentweder trueoder zur falseLaufzeit der Fall ist, gibt es sonst nichts! Da Typen Werte wie folgt klassifizieren, können wir diese Informationen verwenden, um einige grundlegende Eigenschaften Ihres Programms zu bestimmen.

Wenn ich zum Beispiel jemals sehe, dass Sie etwas hinzufügen eund e'wann e : intund e' : String, dann weiß ich, dass etwas nicht stimmt! Tatsächlich kann ich dies markieren und beim Kompilieren einen Fehler auslösen und sagen "Hey, das ergibt überhaupt keinen Sinn!".

Ein leistungsfähigeres Typensystem ermöglicht interessantere Typen, die interessantere Werte klassifizieren. Betrachten wir zum Beispiel eine Funktion

f = fun x -> x

Es ist ziemlich klar f : Something -> Something, aber was soll das Somethingsein? In einem langweiligen Typensystem müssten wir etwas Beliebiges wie spezifizieren Something = int. In einem flexibleren Typensystem könnte man sagen

f : forall a. a -> a

Das heißt "für jeden a, fordnet an an azu a". Dies wollen wir fallgemeiner verwenden und interessantere Programme schreiben.

Außerdem prüft der Compiler, ob der von uns angegebene Klassifikator tatsächlich erfüllt ist, wenn f = fun x -> truedann ein Fehler vorliegt und der Compiler dies sagt!

Also als tldr; Ein Typ ist eine Zeitbeschränkung für die Kompilierung der Werte, die ein Ausdruck zur Laufzeit haben kann.

Geben Sie Konstruktor ein

Einige Typen sind verwandt. Eine Liste von Ganzzahlen ist beispielsweise einer Liste von Zeichenfolgen sehr ähnlich. Dies ist fast so, wie es sortbei ganzen Zahlen sortbei Strings der Fall ist . Wir können uns eine Art Fabrik vorstellen, die diese fast gleichen Typen baut, indem wir ihre Unterschiede verallgemeinern und sie nach Bedarf bauen. Das ist was ein Typkonstruktor ist. Es ist wie eine Funktion von Typ zu Typ, aber etwas eingeschränkter.

Das klassische Beispiel ist eine generische Liste. Ein Typkonstruktor für ist nur die generische Definition

 data List a = Cons a (List a) | Nil

Jetzt Listgibt es eine Funktion, die einen Typ aeiner Liste von Werten dieses Typs zuordnet! In Java-Land werden diese vielleicht "generische Klassen" genannt.

Geben Sie Parameter ein

Ein Typparameter ist nur der Typ, der an einen Typkonstruktor (oder eine Funktion) übergeben wird. Genau wie in der Wertebene haben wir foo(a)einen Parameter, agenauso wie List aein Typparameter a.

Arten

Arten sind ein bisschen schwierig. Die Grundidee ist, dass bestimmte Typen ähnlich sind. Zum Beispiel haben wir alle primitiven Typen in Java int, char, float... die alle verhalten sich , als ob sie die gleiche „Art“ haben. Wenn wir jedoch von den Klassifizierern für Typen selbst sprechen, nennen wir die Klassifizierertypen. Also int : Prim, String : Box, List : Boxed -> Boxed.

Dieses System gibt schöne konkrete Regeln darüber, welche Arten von Typen wir wo verwenden können, genau wie Typen Werte regeln. Es wäre eindeutig Unsinn zu sagen

 List<List>

oder

 List<int>

In Java muss da Listauf einen konkreten Typ angewendet werden, um so verwendet zu werden! Wenn wir uns ihre Arten ansehen List : Boxed -> Boxedund da Boxed -> Boxed /= Boxed, ist das obige ein freundlicher Fehler!

Die meiste Zeit denken wir nicht wirklich über Arten nach und behandeln sie nur als "gesunden Menschenverstand", aber bei schickeren Typsystemen ist es wichtig, darüber nachzudenken.

Eine kleine Illustration dessen, was ich bisher gesagt habe

 value   : type : kind  : ...
 true    : bool : Prim  : ...
 new F() : Foo  : Boxed : ...

Besser lesen als Wikipedia

Wenn Sie sich für so etwas interessieren, würde ich Ihnen wärmstens empfehlen, ein gutes Lehrbuch anzulegen. Typentheorie und PLT im Allgemeinen sind ziemlich umfangreich und ohne eine zusammenhängende Wissensbasis können Sie (oder zumindest ich) herumirren, ohne monatelang irgendwo hin zu kommen.

Zwei meiner Lieblingsbücher sind

  • Typen und Programmiersprache - Ben Pierce
  • Praktische Grundlagen von Programmiersprachen - Bob Harper

Beide sind ausgezeichnete Bücher, die das, worüber ich gerade gesprochen habe, und vieles mehr in schönen, gut erklärten Details vorstellen.


1
Typen sind Sets? Ich mag "Klassifizierer" besser, aber Sie erklären nicht, was dies bedeutet, und ohne ein gutes Verständnis dafür, was ein Typ ist, fällt der Rest Ihrer Antwort irgendwie runter.
Robert Harvey

@RobertHarvey Wie sieht es jetzt aus, ich habe alle Erwähnungen von Sets fallen gelassen :)
Daniel Gratzer

1
Viel besser ...
Robert Harvey

@RobertHarvey Ich finde die Ansicht von Typen als Mengen sehr intuitiv. ZB Der Typ intin Java besteht aus einer Menge von 2 ^ 64 verschiedenen Werten. Die Analogie mit Mengen bricht zusammen, wenn Subtypen involviert werden, aber es ist eine gute anfängliche Intuition, insbesondere wenn Sie algebraische Datentypen berücksichtigen (z. B. kann eine Vereinigung von zwei Typen jedes der Mitglieder beider Typen enthalten; es ist die Vereinigung dieser Mengen). .
Doval

@Doval: Wenn ich eine Klasse schreibe, die einen Kunden beschreibt, wird sie wahrscheinlich eine "Menge" von Kunden darstellen, da ich eine Sammlung von Instanzen erstellen werde. Zu sagen, dass ein Kunde ein Typ ist, weil er eine "Menge" von Kunden beschreibt, ist eine Tautologie. es scheint offensichtlich. Interessanter ist, dass ein Kundentyp die Eigenschaften eines Kunden beschreibt. Dies mit "set" zu erklären, scheint abstrakter zu sein, als es tatsächlich ist. Es sei denn, Sie sind Mathematiker.
Robert Harvey

2

Wie sind diese Dinge richtig definiert?

Sie zeichnen sich durch eine starre, akademische mathematische Grundlage aus, die aussagekräftige Aussagen darüber liefert, was sie sind, wie sie funktionieren und was garantiert ist.

Aber Programmierer müssen das größtenteils nicht wissen. Sie müssen die Konzepte verstehen.

Werte

Beginnen wir mit Werten, da von dort alles aufgebaut wird. Werte sind die Daten, die beim Rechnen verwendet werden. Abhängig von der Herangehensweise sind dies die Werte, mit denen jeder vertraut ist: 42, 3.14, "Wie jetzt braune Kuh", die Personalakte für Jenny unten in der Buchhaltung usw.

Andere Interpretationen von Werten sind Symbole . Die meisten Programmierer verstehen diese Symbole als "Werte" einer Aufzählung. Leftund Rightsind Symbole für die Aufzählung Handedness(ignoriert beidhändig lebende Menschen und Fische).

Unabhängig von der Implementierung sind Werte die verschiedenen Dinge, mit denen die Sprache Berechnungen durchführt.

Typen

Das Problem mit Werten ist, dass nicht alle Berechnungen für alle Werte zulässig sind. 42 + goatmacht nicht wirklich Sinn.

Hier kommen Typen ins Spiel. Typen sind Metadaten, die Teilmengen von Werten definieren. DasHandedness obige Aufzählung ist ein gutes Beispiel. Dieser Typ sagt "nur Leftund Rightdarf hier verwendet werden". Auf diese Weise können Programme sehr früh feststellen, dass bestimmte Vorgänge zu Fehlern führen.

Ein weiterer praktischer Aspekt ist, dass Computer unter der Haube mit Bytes arbeiten. Das Byte 42 könnte die Nummer 42 bedeuten, oder es könnte das Zeichen * bedeuten, oder es könnte Jenny aus der Buchhaltung bedeuten. Typen (in der Praxis weniger theoretisch) helfen auch dabei, die Codierung für die zugrunde liegende Sammlung von Bytes zu definieren, die von Computern verwendet werden.

Arten

Und hier fangen wir an, ein bisschen nach draußen zu gehen. Also, wenn eine Programmiersprache eine Variable hat, die auf einen Typ verweist, welchen Typ hat sie ?

In Java und C # hat es zum Beispiel den Typ Type(der den Typ hat Type, der hat ... und so weiter bis zum Ende). Dies ist das Konzept hinter den Arten . In einigen Sprachen können Sie mit einer Type-Variablen ein bisschen nützlicheres tun als mit Java und C #. Sobald dies geschieht, ist es nützlich zu sagen, dass ich einen Wert möchte, der ein Typ ist, aber auch eine Art von ist IEnumerable<int>. Ta-da! Arten.

Die meisten Programmierer können sich Arten wie generische Java- und C # -Einschränkungen vorstellen. Überlegen Sie public class Foo<T> where T: IComparable{}. In einer Sprache mit Arten wird die T: kindOf(IComparable)Variablendeklaration legal; nicht nur eine spezielle Sache, die Sie in Klassen- und Funktionsdeklarationen tun können.

Geben Sie Konstruktoren ein

Es überrascht vielleicht nicht, dass Typkonstruktoren einfach Konstruktoren für Typen sind . „Aber wie wollen Sie eine Art konstruieren? Typen gerade sind .“. Äh ... nicht so sehr.

Es ist auch nicht überraschend, dass es ziemlich schwierig ist, alle zu bauen nützlichen Teilmengen von Werten die ein Computerprogramm jemals verwenden wird. Mit Hilfe von Typkonstruktoren können Programmierer diese Teilmengen auf sinnvolle Weise "erstellen".

Das am weitesten verbreitete Beispiel eines Typkonstruktor ist eine Array Definition: int[4]. Hier geben Sie den Typkonstruktor 4an, der den Wert verwendet, um ein Array von zu erstellenint s mit 4 Einträgen . Wenn Sie einen anderen Eingabetyp angegeben haben, erhalten Sie einen anderen Ausgabetyp.

Generika sind eine andere Form des Typkonstruktors, bei der ein anderer Typ als Eingabe verwendet wird.

In vielen Sprachen gibt es einen Typkonstruktor, der gerne P -> Reinen Typ erstellt, der eine Funktion darstellt, die einen Typ annimmt Pund einen Typ zurückgibt R.

Der Kontext bestimmt nun, ob eine "Funktion, die einen Typ zurückgibt", ein Typkonstruktor ist oder nicht. Nach meiner (zugegebenermaßen eingeschränkten) Erfahrung lautet die Zeile "Können Sie diesen Typ zur Kompilierungszeit verwenden?". Ja? Geben Sie Konstruktor ein. Nein? Nur eine Funktion.

Typ Parameter

Sie erinnern sich also an die Parameter, die an Typkonstruktoren übergeben wurden? Sie werden allgemein als Typparameter bezeichnet, da die übliche Form eines Typkonstruktors Type[param]oder ist Type<param>.


1
Könnten Sie den Abschnitt über "Arten" präzisieren / erweitern? In Haskell hat ein Typ eine Art *, während ein Typkonstruktor (mit einem Argument) eine Art hat * -> *. Einschränkungen wie (Num a) => a(dh "jeder Typ a, der eine Instanz der NumTypenklasse ist") sind selbst keine Arten. Die Typenklasse Numist keine "Art" selbst, sondern hat die Art * -> Constraint. Ich finde es schwierig, die Haskell-Idee einer Art (von der ich annehme, dass sie eng mit Arten in der Typentheorie verwandt ist?) Mit den von Ihnen angegebenen Beispielen in Beziehung zu setzen.
John Bartholomew

Ich sollte sagen, Ghci :kindBefehl gibt die Art von Numals * -> Constraint. Das könnte spezifisch für GHC sein, ich weiß es nicht.
John Bartholomew

@JohnBartholomew - Haskell-Arten sind eher "Signaturen für Typkonstruktoren". Leider ist mein Haskell nicht annähernd so weit, dass es mir angenehm wäre, zu viel über die Einzelheiten zu sprechen.
Telastyn
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.