Gibt es einen Grund, einen Bottom-Typ in einer Programmiersprache zu haben?

49

Ein unterer Typ ist ein Konstrukt, das hauptsächlich in der mathematischen Typentheorie vorkommt. Es wird auch als leerer Typ bezeichnet. Es ist ein Typ, der keine Werte hat, aber ein Subtyp aller Typen.

Wenn der Rückgabetyp einer Funktion der unterste Typ ist, bedeutet dies, dass sie nicht zurückgegeben wird. Zeitraum. Vielleicht schleift es für immer, oder es löst eine Ausnahme aus.

Was bringt es, diesen seltsamen Typ in einer Programmiersprache zu haben? Es ist nicht so verbreitet, aber es ist in einigen wie Scala und Lisp vorhanden.

GregRos
quelle
2
@SargeBorsch: bist du dir da sicher? Natürlich kann man in C keine voidDaten explizit definieren ...
Basile Starynkevitch
3
@BasileStarynkevitch Es gibt keine Werte vom Typ void, und der Einheitentyp muss einen Wert haben. Wie Sie bereits betont haben, können Sie nicht einmal einen Wert vom Typ deklarieren void, das heißt, es handelt sich nicht einmal um einen Typ, sondern nur um einen speziellen Eckfall in der Sprache.
Sarge Borsch
2
Ja, das C ist in dieser Hinsicht bizarr, insbesondere, wie die Zeiger- und Funktionszeigertypen geschrieben werden. Aber voidin Java ist es fast dasselbe: Nicht wirklich ein Typ und kann keine Werte haben.
Sarge Borsch
3
In der Semantik von Sprachen mit einem Bottom-Typ wird davon ausgegangen, dass der Bottom-Typ keine Werte hat, sondern einen Wert, den Bottom-Wert, der eine Berechnung darstellt, die (normalerweise) nie abgeschlossen wird. Da der unterste Wert ein Wert für jeden Typ ist, kann der unterste Typ ein Untertyp für jeden Typ sein.
Theodore Norvell
4
@BasileStarynkevitch Common Lisp hat den Typ "nil", der keine Werte enthält. Es hat auch den NULL-Typ, der nur einen Wert hat, das Symbol nil(aka, ()), das ein Einheitentyp ist.
Joshua Taylor

Antworten:

33

Ich nehme ein einfaches Beispiel: C ++ vs Rust.

Hier ist eine Funktion zum Auslösen einer Ausnahme in C ++ 11:

[[noreturn]] void ThrowException(char const* message,
                                 char const* file,
                                 int line,
                                 char const* function);

Und hier ist das Äquivalent in Rust:

fn formatted_panic(message: &str, file: &str, line: isize, function: &str) -> !;

Rein syntaktisch ist das Rust-Konstrukt sinnvoller. Beachten Sie, dass das C ++ - Konstrukt einen Rückgabetyp angibt , obwohl es auch angibt, dass er nicht zurückgegeben wird. Das ist ein bisschen komisch.

Standardmäßig erschien die C ++ - Syntax nur mit C ++ 11 (sie wurde oben angeheftet), aber verschiedene Compiler stellten seit einiger Zeit verschiedene Erweiterungen bereit, so dass Analysewerkzeuge von Drittanbietern programmiert werden mussten, um die verschiedenen Möglichkeiten zu erkennen Dieses Attribut könnte geschrieben werden. Es standardisiert zu haben, ist offensichtlich eindeutig überlegen.


Nun zum Nutzen?

Die Tatsache, dass eine Funktion nicht zurückgegeben wird, kann nützlich sein für:

  • Optimierung: Man kann jeden Code danach beschneiden (er wird nicht zurückkehren), es besteht keine Notwendigkeit, die Register zu speichern (da es nicht notwendig ist, sie wiederherzustellen), ...
  • statische Analyse: Es werden eine Reihe potenzieller Ausführungspfade eliminiert
  • Wartbarkeit: (siehe statische Analyse, aber vom Menschen)
Matthieu M.
quelle
6
voidIn Ihrem C ++ - Beispiel wird der Typ der Funktion (ein Teil davon) definiert, nicht der Rückgabetyp. Dies schränkt den Wert ein, den die Funktion annehmen darf return. alles, was sich ins Leere verwandeln lässt (was nichts ist). Wenn die Funktion returns ist, darf kein Wert folgen. Der vollständige Typ der Funktion ist void () (char const*, char const*, int, char const *). + 1 für die Verwendung char constanstelle von const char:-)
Clearer
4
Das bedeutet jedoch nicht, dass es sinnvoller ist, einen Bottom-Typ zu haben, sondern nur, dass es sinnvoll ist, Funktionen zu kommentieren, ob sie als Teil der Sprache zurückgegeben werden oder nicht. Da Funktionen aus verschiedenen Gründen möglicherweise nicht zurückgegeben werden können, ist es anscheinend besser, den Grund in irgendeiner Weise zu kodieren, anstatt einen Sammelbegriff zu verwenden, ähnlich wie das relativ neue Konzept, Funktionen auf der Grundlage ihrer Nebenwirkungen mit Anmerkungen zu versehen.
GregRos
2
Tatsächlich gibt es einen Grund dafür, "Gibt nicht zurück" und "Hat den Rückgabetyp X" unabhängig zu machen: Abwärtskompatibilität für Ihren eigenen Code, da die Aufrufkonvention möglicherweise vom Rückgabetyp abhängt.
Deduplizierer
ist [[noreturn]] par der Syntax oder einem Zusatz von Funktionalität?
Zaibis,
1
[Forts.] Insgesamt würde ich nur sagen, dass eine Diskussion über die Vorteile von ⊥ definieren muss, was als Implementierung von ⊥ zu qualifizieren ist. und ich denke nicht, dass ein Typensystem , das nicht ( a → ⊥) ≤ ( ab ) hat, eine nützliche Implementierung von ⊥ ist. In diesem Sinne erlaubt das SysV x86-64 C ABI (unter anderem) einfach keine Implementierung von ⊥.
Alex Shpilkin
26

Karls Antwort ist gut. Hier ist eine zusätzliche Verwendung, von der ich glaube, dass sie noch niemand erwähnt hat. Die Art von

if E then A else B

sollte ein Typ sein, der alle Werte im Typ von Aund alle Werte im Typ von enthält B. Wenn der Typ von Bist Nothing, kann der Typ des ifAusdrucks der Typ von sein A. Ich werde oft eine Routine erklären

def unreachable( s:String ) : Nothing = throw new AssertionError("Unreachable "+s) 

zu sagen, dass der Code voraussichtlich nicht erreicht wird. Da es sich um den Typ handelt Nothing, unreachable(s)kann es jetzt in jedem ifoder (häufiger) verwendet werden, switchohne dass die Art des Ergebnisses beeinflusst wird. Zum Beispiel

 val colour : Colour := switch state of
         BLACK_TO_MOVE: BLACK
         WHITE_TO_MOVE: WHITE
         default: unreachable("Bad state")

Scala hat so einen Nichts-Typ.

Ein weiterer Anwendungsfall für Nothing(wie in Karls Antwort erwähnt) ist List [Nothing]. Dabei handelt es sich um die Art von Listen, deren Mitglieder jeweils den Typ Nothing haben. Es kann also der Typ der leeren Liste sein.

Die Schlüsseleigenschaft Nothing, die diese Anwendungsfälle funktionieren lässt, ist nicht, dass sie keine Werte haben - obwohl sie beispielsweise in Scala keine Werte haben -, dass sie ein Untertyp jedes anderen Typs sind.

Angenommen, Sie haben eine Sprache, in der jeder Typ denselben Wert enthält - nennen wir es (). In einer solchen Sprache kann der Einheitentyp, der ()den einzigen Wert hat, ein Untertyp jedes Typs sein. Das macht es nicht zu einem Bottom-Typ im Sinne des OP; Dem OP war klar, dass ein Bottom-Typ keine Werte enthält. Da es sich jedoch um einen Typ handelt, der ein Untertyp jedes Typs ist, kann er in etwa die gleiche Rolle spielen wie ein unterer Typ.

Haskell macht die Dinge ein bisschen anders. In Haskell kann ein Ausdruck, der niemals einen Wert erzeugt, das Typschema haben forall a.a. Eine Instanz dieses Typschemas wird mit jedem anderen Typ vereinheitlicht, sodass sie effektiv als unterer Typ fungiert, obwohl (Standard-) Haskell keine Vorstellung von Untertypisierung hat. Beispielsweise hat die errorFunktion aus dem Standardvorspiel ein Typschema forall a. [Char] -> a. Du kannst also schreiben

if E then A else error ""

und der Typ des Ausdrucks Aist für jeden Ausdruck der gleiche wie der Typ von A.

Die leere Liste in Haskell hat das Typschema forall a. [a]. Wenn Aein Ausdruck ist, dessen Typ ein Listentyp ist, dann

if E then A else []

ist ein Ausdruck mit dem gleichen Typ wie A.

Theodore Norvell
quelle
Was ist der Unterschied zwischen dem Typ forall a . [a]und dem Typ [a]in Haskell? Sind Typvariablen in Haskell-Typausdrücken nicht bereits allgemein quantifiziert?
Giorgio,
@Giorgio In Haskell ist die universelle Quantifizierung implizit, wenn klar ist, dass Sie ein Typschema betrachten. Sie können nicht einmal forallin Standard-Haskell 2010 schreiben . Ich habe die Quantifizierung explizit geschrieben, da dies kein Haskell-Forum ist und einige Leute möglicherweise nicht mit Haskells Konventionen vertraut sind. Es gibt also keinen Unterschied, außer dass dies forall a . [a]kein Standard [a]ist.
Theodore Norvell
19

Typen bilden auf zwei Arten ein Monoid und bilden zusammen ein Semiring . Das nennt man algebraische Datentypen . Für endliche Typen bezieht sich dieses Semiren direkt auf das Semiren natürlicher Zahlen (einschließlich Null). Dies bedeutet, dass Sie zählen, wie viele mögliche Werte der Typ hat (ohne „nicht terminierende Werte“).

  • Der unterste Typ (ich werde es nennen Vacuous) hat Nullwerte .
  • Der Einheitentyp hat einen Wert. Ich werde sowohl den Typ als auch seinen Einzelwert nennen ().
  • Komposition (die von den meisten Programmiersprachen direkt über Datensätze / Strukturen / Klassen mit öffentlichen Feldern unterstützt wird) ist eine Produktoperation . Zum Beispiel (Bool, Bool)hat vier mögliche Werte, nämlich (False,False), (False,True), (True,False)und (True,True).
    Der Einheitentyp ist das Identitätselement der Kompositionsoperation. ZB ((), False)und ((), True)sind die einzigen Werte von Typ ((), Bool), daher ist dieser Typ für sich Boolselbst isomorph .
  • Alternative Typen werden in den meisten Sprachen etwas vernachlässigt (OO-Sprachen unterstützen sie irgendwie bei der Vererbung), aber sie sind nicht weniger nützlich. Eine Alternative zwischen zwei Typen Aund hat im BGrunde alle Werte von Aund alle Werte von B, daher Summentyp . Zum Beispiel Either () Boolhat drei Werte, ich werde sie rufen Left (), Right Falseund Right True.
    Der unterste Typ ist das Identitätselement der Summe: Either Vacuous Aenthält nur Werte der Form Right a, da er Left ...keinen Sinn ergibt ( Vacuousenthält keine Werte).

Das Interessante an diesen Monoiden ist, dass, wenn Sie Funktionen in Ihre Sprache einführen , die Kategorie dieser Typen mit den Funktionen als Morphismen eine monoidale Kategorie ist . Hiermit können Sie unter anderem anwendbare Funktoren und Monaden definieren , die sich als hervorragende Abstraktion für allgemeine Berechnungen (möglicherweise mit Nebenwirkungen usw.) in ansonsten rein funktionalen Begriffen herausstellen.

Tatsächlich kann man es ziemlich weit bringen, sich nur um eine Seite des Problems zu kümmern (das Kompositionsmonoid), dann braucht man den Bottom-Typ nicht wirklich explizit. Zum Beispiel hatte selbst Haskell lange Zeit keinen Standardbodentyp. Jetzt heißt es Void.

Betrachtet man aber das Gesamtbild als eine bikartesische geschlossene Kategorie , so entspricht das Typensystem tatsächlich der gesamten Lambda-Rechnung, so hat man im Grunde die perfekte Abstraktion über alles Mögliche in einer Turing-vollständigen Sprache. Ideal für eingebettete domänenspezifische Sprachen, zum Beispiel gibt es ein Projekt zur direkten Kodierung elektronischer Schaltungen auf diese Weise .

Natürlich kann man sagen, dass dies der allgemeine Unsinn aller Theoretiker ist . Sie müssen sich überhaupt nicht mit Kategorietheorie auskennen, um ein guter Programmierer zu sein, aber wenn Sie dies tun, erhalten Sie mächtige und lächerlich allgemeine Möglichkeiten, um über Code zu argumentieren und Invarianten zu beweisen.


mb21 erinnert mich daran, dass dies nicht mit den unteren Werten verwechselt werden sollte . In faulen Sprachen wie Haskell enthält jeder Typ einen unteren "Wert", der als "Wert" bezeichnet wird . Dies ist keine konkrete Sache, die Sie jemals explizit weitergeben könnten, sondern das, was beispielsweise "zurückgegeben" wird, wenn eine Funktion für immer eine Schleife durchläuft. Sogar Haskells VoidTyp enthält den untersten Wert, also den Namen. In diesem Licht hat Haskells unterster Typ tatsächlich einen Wert und sein Einheitentyp zwei Werte, aber in der kategorietheoretischen Diskussion wird dies im Allgemeinen ignoriert.

links herum
quelle
"Der unterste Typ (ich werde es nennen Void)", der nicht mit dem Wert zu verwechseln istbottom , der ein Mitglied eines beliebigen Typs in Haskell ist .
mb21
18

Vielleicht schleift es für immer, oder es löst eine Ausnahme aus.

Klingt nach einem nützlichen Typ, den man in solchen Situationen haben kann, auch wenn sie selten sind.

Auch wenn Nothing(Scalas Name für den untersten Typ) keine Werte List[Nothing]haben kann, unterliegt diese Einschränkung nicht, weshalb sie als Typ einer leeren Liste nützlich ist. Die meisten Sprachen umgehen dies, indem sie eine leere Liste von Zeichenfolgen anders als eine leere Liste von Ganzzahlen formatieren. Dies ist zwar sinnvoll, führt jedoch zu einer ausführlicheren Schreibweise einer leeren Liste, was in einer listenorientierten Sprache ein großer Nachteil ist.

Karl Bielefeldt
quelle
12
„Haskells leere Liste ist ein Typkonstruktor“: Das Relevante an ihr ist sicherlich, dass sie polymorph oder überladen ist - das heißt, die leeren Listen verschiedener Typen sind unterschiedliche Werte, []stellen jedoch alle von ihnen dar und werden instaniert den spezifischen Typ nach Bedarf.
Peter LeFanu Lumsdaine
Interessanterweise : Wenn Sie versuchen , ein leeres Array in den Haskell - Interpreter zu erstellen, erhalten Sie einen ganz bestimmten Wert mit einer sehr unbestimmten Art: [a]. Ebenso :t Left 1Ausbeuten Num a => Either a b. Die tatsächliche Bewertung des Ausdrucks erzwingt die Art von a, aber nicht von b:Either Integer b
John Dvorak
5
Die leere Liste ist ein Wertekonstruktor . Etwas verwirrend ist, dass der betreffende Typkonstruktor denselben Namen hat, die leere Liste selbst jedoch ein Wert und kein Typ ist (es gibt auch Listen auf Typebene, aber das ist ein ganz anderes Thema). Der Teil, mit dem die leere Liste für einen Listentyp funktioniert, ist der forallin ihrem Typ implizierte forall a. [a]. Es gibt ein paar gute Möglichkeiten, darüber nachzudenken forall, aber es braucht einige Zeit, um es wirklich herauszufinden.
David
@PeterLeFanuLumsdaine Genau das bedeutet es, ein Typkonstruktor zu sein. Es bedeutet nur, dass es ein Typ ist, der sich von dem unterscheidet *.
GregRos
2
In Haskell []handelt es sich um einen Typkonstruktor und []einen Ausdruck, der eine leere Liste darstellt. Dies bedeutet jedoch nicht, dass "Haskells leere Liste ein Typkonstruktor ist". Der Kontext macht deutlich, ob []er als Typ oder als Ausdruck verwendet wird. Angenommen, Sie erklären data Foo x = Foo | Bar x (Foo x); Jetzt können Sie Fooes als Typkonstruktor oder als Wert verwenden, aber zufällig haben Sie für beide denselben Namen gewählt.
Theodore Norvell
3

Für die statische Analyse ist es hilfreich zu dokumentieren, dass ein bestimmter Codepfad nicht erreichbar ist. Wenn Sie beispielsweise Folgendes in C # schreiben:

int F(int arg) {
 if (arg != 0)
  return arg + 1; //some computation
 else
  Assert(false); //this throws but the compiler does not know that
}
void Assert(bool cond) { if (!cond) throw ...; }

Der Compiler wird sich beschweren, dass Fin mindestens einem Codepfad nichts zurückgegeben wird. Wenn Assertder Compiler als nicht zurückgegeben markiert werden soll, muss er nicht warnen.

usr
quelle
2

In einigen Sprachen nullhat der Bottom-Typ, da der Subtyp aller Typen genau definiert, für welche Sprachen null verwendet wird (trotz des milden Widerspruchs, nullsowohl sich selbst als auch eine Funktion zu haben, die sich selbst zurückgibt, ohne die allgemeinen Argumente darüber, warum botunbewohnt sein sollte).

Es kann auch als any -> botSammelbegriff für Funktionstypen ( ) verwendet werden, um einen fehlerhaften Versand zu behandeln.

In einigen Sprachen können Sie tatsächlich boteinen Fehler beheben , der verwendet werden kann, um benutzerdefinierte Compilerfehler bereitzustellen.

Telastyn
quelle
11
Nein, ein unterer Typ ist nicht der Einheitentyp. Ein Bottom-Typ hat überhaupt keinen Wert, daher sollte eine Funktion, die einen Bottom-Typ
zurückgibt,
@BasileStarynkevitch - Ich spreche nicht über den Einheitentyp. Der Einheitentyp wird voidin gemeinsamen Sprachen abgebildet (allerdings mit geringfügig unterschiedlicher Semantik für die gleiche Verwendung), nicht null. Sie haben auch Recht, dass die meisten Sprachen nicht null als untersten Typ modellieren.
Telastyn
3
@TheodoreNorvell - frühe Versionen von Tangent haben das getan - obwohl ich der Autor bin, ist das vielleicht ein Betrug. Ich habe die Links nicht für andere gespeichert, und es ist schon eine Weile her, dass ich diese Recherche durchgeführt habe.
Telastyn
1
@Martijn Du kannst aber nullzB einen Zeiger vergleichen, nullum ein Boolesches Ergebnis zu erhalten. Ich denke, die Antworten zeigen, dass es zwei verschiedene Arten von Grundtypen gibt. (a) Sprachen (z. B. Scala), in denen der Typ, der ein Untertyp jedes Typs ist, Berechnungen darstellt, die keine Ergebnisse liefern. Im Grunde ist es ein leerer Typ, obwohl er technisch häufig mit einem nutzlosen Grundwert gefüllt ist, der Nichtbeeinflussung darstellt. (b) Sprachen wie Tangens, in denen der unterste Typ eine Teilmenge jedes anderen Typs ist, weil er einen nützlichen Wert enthält, der auch in jedem anderen Typ enthalten ist - null.
Theodore Norvell
4
Es ist interessant, dass einige Sprachen einen Wert mit einem Typ haben, den Sie nicht deklarieren können (gemeinsam für das Null-Literal), und andere einen Typ haben, den Sie deklarieren können, der aber keine Werte hat (ein traditioneller unterer Typ), und dass sie vergleichbare Rollen ausfüllen .
Martijn
1

Ja, das ist ein ziemlich nützlicher Typ. Während seine Rolle größtenteils innerhalb des Typensystems liegt, gibt es einige Fälle, in denen der unterste Typ offen auftaucht.

Stellen Sie sich eine statisch typisierte Sprache vor, in der Bedingungen Ausdrücke sind (die if-then-else-Konstruktion fungiert also auch als ternärer Operator von C und friends, und es kann eine ähnliche Multi-Way-Case-Anweisung geben). Funktionale Programmiersprachen haben dies, aber es kommt auch in bestimmten imperativen Sprachen vor (seit ALGOL 60). Dann müssen alle Verzweigungsausdrücke letztendlich den Typ des gesamten bedingten Ausdrucks erzeugen. Man könnte einfach verlangen, dass ihre Typen gleich sind (und ich denke, dass dies für den ternären Operator in C der Fall ist), aber dies ist zu restriktiv, insbesondere, wenn die Bedingung auch als bedingte Anweisung verwendet werden kann (und keinen nützlichen Wert zurückgibt). Im Allgemeinen möchte man, dass jeder Verzweigungsausdruck (implizit) konvertierbar ist zu einem gemeinsamen Typ, der der Typ des vollständigen Ausdrucks ist (möglicherweise mit mehr oder weniger komplizierten Einschränkungen, damit der Complier diesen gemeinsamen Typ effektiv findet, vgl. C ++, aber ich werde hier nicht auf diese Details eingehen).

Es gibt zwei Arten von Situationen, in denen eine allgemeine Art der Konvertierung die notwendige Flexibilität solcher bedingten Ausdrücke ermöglicht. Eines ist bereits erwähnt, wobei der Ergebnistyp der Einheitentyp istvoid; Dies ist natürlich ein Supertyp aller anderen Typen, und die (triviale) Konvertierung jedes beliebigen Typs ermöglicht die Verwendung des bedingten Ausdrucks als bedingte Anweisung. Das andere Problem betrifft Fälle, in denen der Ausdruck zwar einen nützlichen Wert zurückgibt, einer oder mehrere Zweige jedoch keinen erzeugen können. Sie lösen normalerweise eine Ausnahme aus oder beinhalten einen Sprung, und es wäre sinnlos, wenn sie (auch) einen Wert des Typs des gesamten Ausdrucks (von einem nicht erreichbaren Punkt aus) erzeugen müssten. Es ist diese Art von Situation, die angemessen gehandhabt werden kann, indem Ausnahmebedingungsklauseln, Sprünge und Aufrufe gegeben werden, die einen solchen Effekt haben, nämlich den untersten Typ, den man (trivial) in einen anderen Typ umwandeln kann.

Ich würde vorschlagen, einen solchen Bottom-Typ *zu schreiben , um dessen Konvertierbarkeit in einen beliebigen Typ vorzuschlagen. Es kann intern anderen nützlichen Zwecken dienen, zum Beispiel beim Versuch, einen Ergebnistyp für eine rekursive Funktion abzuleiten, die keine deklariert. Der Typinferencer könnte den Typ *einem beliebigen rekursiven Aufruf zuweisen , um eine Henne-Ei-Situation zu vermeiden. Der tatsächliche Typ wird durch nicht rekursive Zweige bestimmt, und die rekursiven werden in den allgemeinen Typ der nicht rekursiven Zweige konvertiert. Wenn es überhaupt keine nicht-rekursiven Verzweigungen *gibt, bleibt der Typ erhalten und zeigt korrekt an, dass es für die Funktion nicht möglich ist, jemals von der Rekursion zurückzukehren. Davon abgesehen und als Ergebnistyp für Ausnahmefunktionen kann man verwenden*als Komponententyp von Folgen der Länge 0, zum Beispiel der leeren Liste; Nochmals, wenn jemals ein Element aus einem Ausdruck vom Typ [*](unbedingt leere Liste) ausgewählt wird, zeigt der resultierende Typ *korrekt an, dass dies niemals ohne Fehler zurückgegeben werden kann.

Marc van Leeuwen
quelle
Könnte die Idee also var foo = someCondition() ? functionReturningBar() : functionThatAlwaysThrows()auf die Art von fooas schließen Bar, da der Ausdruck niemals etwas anderes ergeben könnte?
Superkatze
1
Sie haben soeben den Einheitentyp beschrieben - zumindest im ersten Teil Ihrer Antwort. Eine Funktion, die den Einheitentyp zurückgibt, ist die gleiche, die voidin C als "return" deklariert wurde. Der zweite Teil Ihrer Antwort, in dem Sie über einen Typ für eine Funktion sprechen, die niemals zurückgibt, oder eine Liste ohne Elemente - das ist in der Tat der unterste Typ! (Es wird oft _|_eher als geschrieben als *. Ich weiß nicht warum. Vielleicht, weil es wie ein (menschlicher)
Hintern
2
Um Zweifel zu vermeiden: "Gibt nichts Nützliches zurück" unterscheidet sich von "Gibt nicht zurück". die erste wird durch den Einheitentyp dargestellt; die zweite vom Typ Bottom.
Andrewf
@andrewf: Ja ich verstehe den Unterschied. Meine Antwort ist etwas langwierig, aber der Punkt, den ich ansprechen wollte, ist, dass der Einheitentyp und der Bodentyp beide (unterschiedliche, aber) vergleichbare Rollen spielen, damit bestimmte Ausdrücke flexibler (aber immer noch sicher) verwendet werden können.
Marc van Leeuwen
@supercat: Ja das ist die Idee. Derzeit in C ++ , die illegal ist, obwohl es gültig wäre , wenn functionThatAlwaysThrows()durch eine explizite ersetzt wurden throw, durch spezielle Sprache in der Norm. Einen Typ zu haben, der dies tut, wäre eine Verbesserung.
Marc van Leeuwen
0

In einigen Sprachen können Sie eine Funktion mit Anmerkungen versehen, um sowohl dem Compiler als auch den Entwicklern mitzuteilen, dass ein Aufruf dieser Funktion nicht zurückgegeben wird (und wenn die Funktion so geschrieben ist, dass sie zurückgegeben werden kann, lässt der Compiler dies nicht zu ). Das ist eine nützliche Sache zu wissen, aber am Ende können Sie eine solche Funktion wie jede andere aufrufen. Der Compiler kann die Informationen zur Optimierung verwenden, um Warnungen über toten Code auszugeben usw. Es gibt also keinen sehr zwingenden Grund, diesen Typ zu haben, aber auch keinen sehr zwingenden Grund, ihn zu vermeiden.

In vielen Sprachen kann eine Funktion "void" zurückgeben. Was das genau bedeutet, hängt von der Sprache ab. In C bedeutet dies, dass die Funktion nichts zurückgibt. In Swift bedeutet dies, dass die Funktion ein Objekt mit nur einem möglichen Wert zurückgibt. Da es nur einen möglichen Wert gibt, benötigt dieser Wert null Bits und erfordert keinen Code. In beiden Fällen ist das nicht dasselbe wie "bottom".

"bottom" wäre ein Typ ohne mögliche Werte. Es kann niemals existieren. Wenn eine Funktion "bottom" zurückgibt, kann sie nicht zurückgeben, da es keinen Wert vom Typ "bottom" gibt, den sie zurückgeben könnte.

Wenn ein Sprachdesigner Lust hat, gibt es keinen Grund, diesen Typ nicht zu haben. Die Implementierung ist nicht schwierig (Sie können sie genau wie eine Funktion implementieren, die void zurückgibt und als "don't return" markiert ist). Sie können keine Zeiger auf Funktionen, die bottom zurückgeben, mit Zeigern auf Funktionen, die void zurückgeben, mischen, da sie nicht vom selben Typ sind.

gnasher729
quelle