Was ist der Kompromiss für die Typinferenz?


29

Es scheint, dass alle neuen Programmiersprachen oder zumindest diejenigen, die populär geworden sind, Typinferenz verwenden. Sogar Javascript hat Typen und Typinferenz durch verschiedene Implementierungen (Acscript, typescript etc). Es sieht toll aus, aber ich frage mich, ob es Kompromisse gibt oder warum Java oder die alten guten Sprachen keine Typinferenz haben

  • Wenn Sie eine Variable in Go deklarieren, ohne ihren Typ anzugeben (mithilfe von var ohne Typ oder der Syntax: =), wird der Typ der Variablen aus dem Wert auf der rechten Seite abgeleitet.
  • D ermöglicht das Schreiben großer Codefragmente ohne redundante Typangabe, wie dies bei dynamischen Sprachen der Fall ist. Andererseits werden durch statische Inferenz Typen und andere Codeeigenschaften abgeleitet, wodurch sowohl die statische als auch die dynamische Welt optimal genutzt werden.
  • Die Typinferenzmaschine in Rust ist ziemlich schlau. Es ist mehr als nur die Art des r-Werts während einer Initialisierung zu betrachten. Es sieht auch so aus, als würde die Variable später verwendet, um auf ihren Typ zu schließen.
  • Swift verwendet die Typinferenz, um den entsprechenden Typ zu ermitteln. Mit der Typinferenz kann ein Compiler den Typ eines bestimmten Ausdrucks automatisch ableiten, wenn er Ihren Code kompiliert, indem er einfach die von Ihnen angegebenen Werte untersucht.

3
In C # wird in den allgemeinen Richtlinien nicht immer angegeben , vardass dies die Lesbarkeit beeinträchtigen kann.
Mephy

5
"... oder warum sagen wir mal Java oder die alten guten Sprachen haben keine Typinferenz" Die Gründe sind wahrscheinlich historisch; ML erschien 1 Jahr nach C laut Wikipedia und hatte Typinferenz. Java versuchte, C ++ - Entwickler anzusprechen . C ++ begann als Erweiterung von C, und das Hauptanliegen von C bestand darin, einen portablen Wrapper über Assembly zu erstellen und einfach zu kompilieren. Was auch immer es wert ist, ich habe gelesen, dass die Untertypisierung die Typinferenz auch im allgemeinen Fall unentscheidbar macht.
Doval

3
@Doval Scala scheint ziemlich gut darin zu sein, Typen abzuleiten, für eine Sprache, die die Vererbung von Subtypen unterstützt. Es ist nicht so gut wie eine der Sprachen der ML-Familie, aber es ist wahrscheinlich so gut, wie Sie es sich wünschen könnten, wenn man das Design der Sprache bedenkt.
KChaloux

12
Es lohnt sich , einen Unterschied zwischen dem, Typ Abzug (monodirectional, wie C # varund C ++ auto) und Typinferenz (bidirektional, wie Haskell let). Im ersteren Fall kann der Typ eines Namens nur von seinem Initialisierer abgeleitet werden - seine Verwendung muss dem Typ des Namens folgen. Im letzteren Fall kann der Typ eines Namens auch aus seiner Verwendung abgeleitet werden. Dies ist hilfreich, da Sie []unabhängig vom Elementtyp einfach für eine leere Sequenz oder newEmptyMVarfür eine neue nullveränderliche Referenz schreiben können , unabhängig vom Referenten Art.
Jon Purdy

Typinferenz kann unglaublich kompliziert zu implementieren sein, besonders effizient. Die Leute wollen nicht, dass ihre Kompilierungskomplexität durch komplexere Ausdrücke exponentiell in die Luft sprengt. Interessant zu lesen: cocoawithlove.com/blog/2016/07/12/type-checker-issues.html
Alexander - Reinstate Monica

Antworten:


43

Haskell Typ - System ist vollständig inferrable (abgesehen von polymorpher Rekursion, bestimmte Spracherweiterungen und die gefürchtete Monomorphie Beschränkung ), noch Programmierer immer noch häufig Typenannotationen im Quellcode zur Verfügung stellen , auch wenn sie nicht brauchen. Warum?

  1. Typanmerkungen dienen als Dokumentation . Dies gilt insbesondere für Typen, die so ausdrucksstark sind wie die von Haskell. Wenn man den Namen und den Typ einer Funktion annimmt, kann man normalerweise ziemlich gut erraten, was die Funktion tut, insbesondere wenn die Funktion parametrisch polymorph ist.
  2. Typanmerkungen können die Entwicklung vorantreiben . Das Schreiben einer Typensignatur vor dem Schreiben des Funktionskörpers fühlt sich wie eine testgetriebene Entwicklung an. Nach meiner Erfahrung funktioniert die Kompilierung einer Haskell-Funktion normalerweise beim ersten Mal. (Das erspart natürlich keine automatisierten Tests!)
  3. Explizite Typen können Ihnen dabei helfen, Ihre Annahmen zu überprüfen . Wenn ich versuche, einen Code zu verstehen, der bereits funktioniert, speichere ich ihn häufig mit den meines Erachtens richtigen Typanmerkungen. Wenn der Code immer noch kompiliert wird, weiß ich, dass ich ihn verstanden habe. Wenn nicht, habe ich die Fehlermeldung gelesen.
  4. Mit Textsignaturen können Sie polymorphe Funktionen spezialisieren . In seltenen Fällen ist eine API aussagekräftiger oder nützlicher, wenn bestimmte Funktionen nicht polymorph sind. Der Compiler wird sich nicht beschweren, wenn Sie einer Funktion einen weniger allgemeinen Typ geben, als man vermuten würde. Das klassische Beispiel ist map :: (a -> b) -> [a] -> [b]. Die allgemeinere Form ( fmap :: Functor f => (a -> b) -> f a -> f b) gilt für alle Functors, nicht nur für Listen. Aber es war der Meinung, dass mapdies für Anfänger leichter zu verstehen wäre, und so lebt es neben seinem größeren Bruder weiter.

Im Großen und Ganzen sind die Nachteile eines statisch getippten, aber nicht ableitbaren Systems fast dieselben wie die Nachteile des statischen Tippens im Allgemeinen. Eine abgenutzte Diskussion auf dieser Website und auf anderen (Googeln mit "statischen Tippnachteilen" bringt Ihnen Hunderte Seiten von Flammenkriegen). Natürlich werden einige der genannten Nachteile durch die geringere Anzahl von Typanmerkungen in einem inferrierbaren System verbessert. Außerdem hat die Typinferenz ihre eigenen Vorteile: Ohne die Typinferenz wäre eine lochgesteuerte Entwicklung nicht möglich.

Java * beweist, dass eine Sprache, die zu viele Typanmerkungen erfordert, ärgerlich wird, aber mit zu wenig verlieren Sie die oben beschriebenen Vorteile. Sprachen mit Opt-Out-Typ-Inferenz stellen ein annehmbares Gleichgewicht zwischen den beiden Extremen her.

* Sogar Java, dieser große Sündenbock, führt eine gewisse lokale Inferenz durch. In einer Anweisung wie Map<String, Integer> = new HashMap<>();muss der generische Typ des Konstruktors nicht angegeben werden. Auf der anderen Seite sind ML- ähnliche Sprachen in der Regel global nicht ableitbar.


2
Aber Sie sind kein Anfänger;) Sie müssen ein Verständnis für Typklassen und -arten haben, bevor Sie wirklich "bekommen" Functor. Listet mapnicht erfahrene Haskeller auf und ist ihnen mit größerer Wahrscheinlichkeit vertraut.
Benjamin Hodgson

1
Würden Sie C # varals Beispiel für Typinferenz betrachten?
Benjamin Hodgson

1
Ja, C # varist ein gutes Beispiel.
Doval

1
Die Seite markiert diese Diskussion bereits als zu lang, daher ist dies meine letzte Antwort. Im ersteren muss der Compiler den Typ bestimmen x; In letzterem gibt es keinen zu bestimmenden Typ, alle Typen sind bekannt und Sie müssen nur prüfen, ob der Ausdruck sinnvoll ist. Der Unterschied wird wichtiger, wenn Sie an trivialen Beispielen vorbeigehen, und xwird an mehreren Stellen verwendet. Der Compiler muss dann die Stellen überprüfen x, an denen ermittelt wird. 1) Ist es möglich, xeinen Typ so zuzuweisen, dass der Code eine Typprüfung durchführt? 2) Wenn ja, welchen allgemeinen Typ können wir zuordnen?
Doval

1
Beachten Sie, dass die new HashMap<>();Syntax nur in Java 7 hinzugefügt wurde und die Lambdas in Java 8 eine Menge "realer" Typinferenzen ermöglichen.
Michael Borgwardt

8

In C # tritt eine Typinferenz zur Kompilierungszeit auf, sodass die Laufzeitkosten Null sind.

Wird aus Gründen des Stils varfür Situationen verwendet, in denen es entweder unpraktisch oder unnötig ist, den Typ manuell anzugeben. Linq ist eine solche Situation. Ein anderer ist:

var s = new SomeReallyLongTypeNameWith<Several, Type, Parameters>(andFormal, parameters);

ohne das Sie Ihren wirklich langen Typnamen (und die Typparameter) wiederholen würden, anstatt einfach zu sagen var.

Verwenden Sie den tatsächlichen Namen des Typs, wenn dieser explizit angegeben wird, um die Übersichtlichkeit des Codes zu verbessern.

Es gibt Situationen, in denen keine Typinferenz verwendet werden kann, z. B. Member-Variablendeklarationen, deren Werte zum Zeitpunkt der Erstellung festgelegt werden, oder in denen Sie wirklich möchten, dass Intellisense ordnungsgemäß funktioniert (die IDE von Hackerrank erkennt die Member der Variablen nur, wenn Sie den Typ explizit deklarieren). .


16
"In C # tritt die Typinferenz zur Kompilierungszeit auf, sodass die Laufzeitkosten Null sind." Typinferenz tritt per Definition zur Kompilierungszeit auf, so dass dies in jeder Sprache der Fall ist.
Doval

So viel besser.
Robert Harvey

1
@Doval Es ist möglich, während der JIT-Kompilierung eine Typinferenz durchzuführen, was eindeutig Laufzeitkosten verursacht.
Jules

6
@Jules Wenn Sie soweit sind, das Programm auszuführen, wurde es bereits typüberprüft. Es gibt nichts mehr zu schließen. Was Sie sprechen, wird normalerweise nicht als Typinferenz bezeichnet, obwohl ich nicht sicher bin, was der richtige Begriff ist.
Doval

7

Gute Frage!

  1. Da der Typ nicht explizit mit Anmerkungen versehen ist, kann das Lesen des Codes manchmal erschwert werden, was zu weiteren Fehlern führt. Richtig verwendet es natürlich macht den Code sauberer und mehr lesbar. Wenn Sie ein Pessimist und denken sind , dass die meisten Programmierer schlecht sind (oder zu arbeiten , wo die meisten Programmierer sind schlecht), wird dies ein Netto-Verlust.
  2. Der Typinferenzalgorithmus ist zwar relativ einfach, aber nicht kostenlos . Diese Art von Dingen erhöht die Kompilierzeit geringfügig.
  3. Da der Typ nicht explizit mit Anmerkungen versehen ist, kann Ihre IDE nicht genau erraten, was Sie tun möchten, wodurch die automatische Vervollständigung und ähnliche Hilfsprogramme während des Deklarationsprozesses beschädigt werden.
  4. In Kombination mit Funktionsüberladungen können Sie gelegentlich in Situationen geraten, in denen der Typinferenzalgorithmus nicht über den einzuschlagenden Pfad entscheiden kann, was zu hässlicheren Beschriftungen im Casting-Stil führt. (Dies passiert zum Beispiel ziemlich häufig mit der anonymen Funktionssyntax von C #).

Und es gibt noch mehr esoterische Sprachen, die ihre Verrücktheit nicht ohne explizite Typanmerkungen vollbringen können. Bisher gibt es keine, von denen ich weiß, dass sie allgemein / populär / vielversprechend genug sind, um sie zu erwähnen, außer im Vorbeigehen.


1
Autocomplete macht keinen Unterschied bei der Typinferenz. Es ist egal, wie der Typ entschieden wurde, nur was der Typ hat. Und die Probleme mit C # -Lambdas haben nichts mit Inferenz zu tun, sondern damit, dass die Lambdas polymorph sind, aber das Typensystem nicht damit zurechtkommt - was nichts mit Inferenz zu tun hat.
DeadMG

2
@deadmg - Wenn die Variable einmal deklariert ist, gibt es kein Problem, aber während Sie die Variablendeklaration eingeben, können die Optionen auf der rechten Seite nicht auf den deklarierten Typ beschränkt werden, da es keinen deklarierten Typ gibt.
Telastyn

@deadmg - bei den Lambdas ist mir nicht klar, was du meinst, aber ich bin mir ziemlich sicher, dass es nicht ganz richtig ist, wenn ich verstehe, wie die Dinge funktionieren. Sogar etwas so Einfaches wie var foo = x => x;scheitert, weil die Sprache xhier schließen muss und nichts weiter zu tun hat. Wenn die Lambdas erstellt werden, werden sie als explizit typisierte Delegaten Func<int, int> foo = x => xin CIL erstellt, so wie Func<int, int> foo = new Func<int, int>(x=>x);das Lambda als generierte explizit typisierte Funktion erstellt wird. Für mich ist der Rückschluss auf die Art ein x
wesentlicher

3
@Telastyn Es ist nicht wahr, dass die Sprache nichts zu tun hat. Alle Informationen, die zum Eingeben des Ausdrucks benötigt werden, x => xsind im Lambda selbst enthalten - es verweist nicht auf Variablen aus dem umgebenden Bereich. Jede vernünftige funktionale Sprache würde richtig schließen , dass seine Art ist „für alle Arten a, a -> a“. Ich denke, was DeadMG zu sagen versucht, ist, dass dem Typensystem von C # die Principal-Type-Eigenschaft fehlt, was bedeutet, dass es immer möglich ist, den allgemeinsten Typ für einen Ausdruck herauszufinden. Es ist sehr leicht zu zerstören.
Doval

@Doval - enh ... es gibt keine generischen Funktionsobjekte in C #, die sich meiner Meinung nach geringfügig von dem unterscheiden, worüber Sie beide sprechen, auch wenn dies zu denselben Problemen führt.
Telastyn

4

Es sieht toll aus, aber ich frage mich, ob es Kompromisse gibt oder warum Java oder die alten guten Sprachen keine Typinferenz haben

Java ist hier eher die Ausnahme als die Regel. Selbst C ++ (von dem ich glaube, dass es eine "gute alte Sprache" ist :)) unterstützt autoseit dem C ++ 11-Standard die Typinferenz mit dem Schlüsselwort. Es funktioniert nicht nur mit Variablendeklaration, sondern auch als Funktionsrückgabetyp, was besonders bei einigen komplexen Vorlagenfunktionen nützlich ist.

Implizite Typisierung und Inferenz haben viele gute Anwendungsfälle, und es gibt auch einige Anwendungsfälle, bei denen Sie dies wirklich nicht tun sollten. Dies ist manchmal Geschmackssache und auch Gegenstand von Debatten.

Dass es aber zweifellos gute Anwendungsfälle gibt, ist für sich genommen ein legitimer Grund für jede Sprache, diese umzusetzen. Es ist auch kein schwer zu implementierendes Feature, keine Laufzeitstrafe und hat keinen wesentlichen Einfluss auf die Kompilierzeit.

Ich sehe keinen wirklichen Nachteil darin, dem Entwickler die Möglichkeit zu geben, Typinferenz zu verwenden.

Einige Antwortende überlegen, wie gut es manchmal ist, explizit zu tippen, und sie haben mit Sicherheit Recht. Wird die implizite Typisierung jedoch nicht unterstützt, bedeutet dies, dass eine Sprache die ganze Zeit explizite Typisierung erzwingt.

Der eigentliche Nachteil ist also, dass eine Sprache keine implizite Typisierung unterstützt, da hiermit angegeben wird, dass es keinen legitimen Grund für den Entwickler gibt, diese zu verwenden, was nicht der Fall ist.


1

Der Hauptunterschied zwischen einem Inferenzsystem vom Typ Hindley-Milner und einem Inferenzsystem vom Typ Go ist die Richtung des Informationsflusses. In HM fließen Typinformationen über die Vereinheitlichung vorwärts und rückwärts. Geben Sie in "Gehe zu" den Informationsfluss nur vorwärts ein: Er berechnet nur vorwärts gesetzte Ersetzungen.

HM-Typinferenz ist eine großartige Innovation, die mit polymorph getippten funktionalen Sprachen gut funktioniert, aber Go-Autoren würden wahrscheinlich argumentieren, dass es zu viel zu tun versucht:

  1. Die Tatsache, dass Informationen sowohl vorwärts als auch rückwärts fließen, bedeutet, dass die Inferenz des HM-Typs eine sehr nichtlokale Angelegenheit ist. Ohne Typanmerkungen kann jede Codezeile in Ihrem Programm zur Eingabe einer einzelnen Codezeile beitragen. Wenn Sie nur eine Vorwärtsersetzung haben, wissen Sie, dass die Quelle eines Typfehlers im Code liegen muss, der Ihrem Fehler vorangeht. nicht der Code, der danach kommt.

  2. Bei der Inferenz von HM-Typen neigen Sie dazu, in Einschränkungen zu denken: Wenn Sie einen Typ verwenden, schränken Sie die möglichen Typen ein. Am Ende des Tages kann es einige Typvariablen geben, die völlig uneingeschränkt bleiben. Die Erkenntnis der HM-Typinferenz ist, dass diese Typen wirklich keine Rolle spielen und daher zu polymorphen Variablen verarbeitet werden. Dieser zusätzliche Polymorphismus kann jedoch aus einer Reihe von Gründen unerwünscht sein. Erstens, wie einige Leute herausgestellt haben, könnte dieser zusätzliche Polymorphismus unerwünscht sein: HM schließt einen gefälschten, polymorphen Typ für einen gefälschten Code ab und führt später zu seltsamen Fehlern. Zweitens kann dies Konsequenzen für das Laufzeitverhalten haben, wenn ein Typ polymorph bleibt. Zum Beispiel ist ein zu polymorphes Zwischenergebnis der Grund, warum 'zeigen. read 'wird in Haskell als mehrdeutig angesehen; als weiteres Beispiel


2
Leider scheint dies eine gute Antwort auf eine andere Frage zu sein. OP fragt nach "Go-Style-Typinferenz" im Vergleich zu Sprachen, die überhaupt keine Typinferenz haben, nicht nach "Go-Style" im Vergleich zu HM.
Ixrec

-2

Es schadet der Lesbarkeit.

Vergleichen Sie

var stuff = someComplexFunction()

vs

List<BusinessObject> stuff = someComplexFunction()

Dies ist vor allem dann ein Problem, wenn Sie außerhalb einer IDE wie auf einem Github lesen.


4
dies scheint nicht zu bieten alles wesentliche über gemacht Punkte und erläuterte vor 6 Antworten
gnat

Wenn Sie anstelle von "Komplex nicht lesbar" den richtigen Namen verwenden, varkann dieser verwendet werden, ohne die Lesbarkeit zu beeinträchtigen. var objects = GetBusinessObjects();
Fabio

Ok, aber dann verlierst du das Typsystem in die Variablennamen. Es ist wie ungarische Notation. Nach Umgestaltungen werden Sie mit größerer Wahrscheinlichkeit Namen erhalten, die nicht mit dem Code übereinstimmen. Im Allgemeinen führt der funktionale Stil dazu, dass Sie die natürlichen Namespace-Vorteile verlieren, die Klassen bieten. Sie erhalten also mehr squareArea () anstelle von square.area ().
Miguel
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.