Ich lese Code viel häufiger als ich Code schreibe, und ich gehe davon aus, dass die meisten Programmierer, die an industrieller Software arbeiten, dies tun. Der Vorteil von Typinferenz, den ich annehme, ist weniger Ausführlichkeit und weniger geschriebener Code. Wenn Sie jedoch häufiger Code lesen, möchten Sie wahrscheinlich lesbaren Code.
Der Compiler fügt den Typ hinzu. Dafür gibt es alte Algorithmen. Die eigentliche Frage ist jedoch, warum ich als Programmierer den Typ meiner Variablen ableiten möchte, wenn ich den Code lese. Ist es nicht schneller für jemanden, nur den Typ zu lesen, als zu überlegen, welcher Typ vorhanden ist?
Edit: Abschließend verstehe ich, warum es sinnvoll ist. Aber in der Kategorie der Sprachfunktionen sehe ich es in einem Eimer mit Überladung des Operators - nützlich in einigen Fällen, aber die Lesbarkeit beeinträchtigend, wenn es missbraucht wird.
quelle
Antworten:
Werfen wir einen Blick auf Java. Java kann keine Variablen mit abgeleiteten Typen haben. Dies bedeutet, dass ich den Typ häufig buchstabieren muss, auch wenn es für einen menschlichen Leser völlig offensichtlich ist, um welchen Typ es sich handelt:
Und manchmal ist es einfach ärgerlich, den ganzen Typ zu buchstabieren.
Diese wortreiche statische Eingabe behindert mich, den Programmierer. Bei den meisten Typanmerkungen handelt es sich um sich wiederholende Zeilenumbrüche, die keine Inhalte enthalten, wie wir sie bereits kennen. Ich mag jedoch das statische Tippen, da es beim Erkennen von Fehlern sehr hilfreich sein kann. Daher ist die Verwendung des dynamischen Tippens nicht immer eine gute Antwort. Typinferenz ist das Beste aus beiden Welten: Ich kann die irrelevanten Typen weglassen, aber trotzdem sicher sein, dass mein Programm (Typ-) auscheckt.
Während die Typinferenz für lokale Variablen sehr nützlich ist, sollte sie nicht für öffentliche APIs verwendet werden, die eindeutig dokumentiert werden müssen. Und manchmal sind die Typen wirklich wichtig, um zu verstehen, was im Code vor sich geht. In solchen Fällen wäre es töricht, sich nur auf die Typinferenz zu verlassen.
Es gibt viele Sprachen, die Typinferenz unterstützen. Beispielsweise:
C ++. Das
auto
Schlüsselwort löst eine Typinferenz aus. Ohne das wäre es die Hölle, die Typen für Lambdas oder für Einträge in Containern zu buchstabieren.C #. Sie können Variablen mit deklarieren
var
, was eine begrenzte Form der Typinferenz auslöst. Es verwaltet immer noch die meisten Fälle, in denen Sie Typinferenz möchten. An bestimmten Stellen kann man den Typ komplett weglassen (zB bei Lambdas).Haskell und jede Sprache in der ML-Familie. Obwohl die hier verwendete spezifische Variante der Typinferenz ziemlich mächtig ist, werden immer noch häufig Typanmerkungen für Funktionen angezeigt, und zwar aus zwei Gründen: Die erste ist die Dokumentation, und die zweite ist eine Überprüfung, ob die Typinferenz tatsächlich die erwarteten Typen gefunden hat. Wenn es eine Diskrepanz gibt, gibt es wahrscheinlich eine Art Fehler.
quelle
int
beliebige numerische Variable handelt , einschließlich einer geradenchar
. Ich verstehe auch nicht, warum Sie den gesamten Typ für den buchstabieren möchten,Entry
wenn Sie nur den Klassennamen eingeben und Ihre IDE den erforderlichen Import durchführen lassen können. Der einzige Fall, in dem Sie den ganzen Namen buchstabieren müssen, ist, wenn Sie eine Klasse mit demselben Namen in Ihrem eigenen Paket haben. Aber es scheint mir trotzdem ein schlechtes Design zu sein.int
Beispiel schrieb , dachte ich über das (meiner Meinung nach ziemlich vernünftige) Verhalten der meisten Sprachen nach, bei denen es sich um Typinferenz handelt. Sie schließen normalerweise auf einint
oderInteger
oder was auch immer es in dieser Sprache heißt. Das Schöne an der Typinferenz ist, dass sie immer optional ist. Sie können immer noch einen anderen Typ angeben, wenn Sie einen benötigen. In Bezug auf dasEntry
Beispiel: Guter Punkt, ich werde es durch ersetzenMap.Entry<Integer, Map<Integer, SomeObject<SomeObject, T>>>
. Java hat nicht einmal Typ-Aliase :(colKey
sowohl offensichtlich als auch irrelevant: Wir kümmern uns nur darum, dass es als zweites Argument von geeignet istdoSomethingWith
. Wenn ich diese Schleife in eine Funktion extrahieren(key1, key2, value)
würde, die eine Iterable von -triples ergibt, wäre die allgemeinste Signatur<K1, K2, V> Iterable<TableEntry<K1, K2, V>> flattenTable(Map<K1, Map<K2, V>> table)
. Innerhalb dieser Funktion ist die tatsächliche Art voncolKey
(Integer
, nichtK2
) absolut irrelevant.View.OnClickListener listener = new View.OnClickListener()
. Sie würden den Typ immer noch kennen, auch wenn der Programmierer "faul" wäre und ihn auf "verkürzt"var listener = new View.OnClickListener
(falls dies möglich war). Diese Art von Redundanz ist üblich - Ich werde keine guesstimate hier riskieren - und entfernen sie sich von Stammzellen über Leser Zukunft zu denken. Jedes Sprachmerkmal sollte mit Vorsicht verwendet werden, das stelle ich nicht in Frage.Es ist wahr, dass Code viel öfter gelesen wird als geschrieben. Das Lesen nimmt jedoch auch Zeit in Anspruch, und zwei Codebildschirme sind schwerer zu navigieren und zu lesen als ein Codebildschirm. Daher müssen wir Prioritäten setzen, um das beste Verhältnis zwischen nützlichen Informationen und Leseaufwand zu erzielen. Dies ist ein allgemeines UX-Prinzip: Zu viele Informationen überwältigen und beeinträchtigen die Wirksamkeit der Benutzeroberfläche.
Und es ist meine Erfahrung, dass oft der genaue Typ nicht wichtig ist. Sicher haben Sie manchmal Ausdrücke Nest:
x + y * z
,monkey.eat(bananas.get(i))
,factory.makeCar().drive()
. Jeder dieser Ausdrücke enthält Unterausdrücke, die einen Wert ergeben, dessen Typ nicht ausgeschrieben ist. Sie sind jedoch vollkommen klar. Wir sind damit einverstanden, den Typ nicht angegeben zu lassen, da es einfach genug ist, aus dem Kontext herauszufinden, und das Ausschreiben würde mehr schaden als nützen (das Verständnis des Datenflusses beeinträchtigen, wertvollen Bildschirm- und Kurzzeitspeicherplatz in Anspruch nehmen).Ein Grund, Ausdrücke nicht zu verschachteln, als gäbe es kein Morgen, ist, dass die Zeilen lang werden und der Wertefluss unklar wird. Das Einführen einer temporären Variablen hilft dabei, erlegt eine Reihenfolge auf und benennt ein Teilergebnis. Nicht alles, was von diesen Aspekten profitiert, profitiert auch davon, dass sein Typ festgelegt wird:
Ist es wichtig, ob
user
es sich um ein Entitätsobjekt, eine Ganzzahl, eine Zeichenfolge oder etwas anderes handelt? In den meisten Fällen reicht es nicht aus, zu wissen, dass es einen Benutzer darstellt, aus der HTTP-Anforderung stammt und zum Abrufen des Namens verwendet wird, der in der unteren rechten Ecke der Antwort angezeigt werden soll.Und wenn es tut Angelegenheit, ist der Autor frei , die Art zu schreiben. Dies ist eine Freiheit, die verantwortungsbewusst genutzt werden muss, aber das Gleiche gilt für alles andere, was die Lesbarkeit verbessern kann (Variablen- und Funktionsnamen, Formatierung, API-Design, Leerraum). Tatsächlich besteht die Konvention in Haskell und ML (wo alles ohne zusätzlichen Aufwand geschlossen werden kann) darin, die Typen von nicht-lokalen Funktionsfunktionen und gegebenenfalls auch von lokalen Variablen und Funktionen aufzuschreiben. Nur Anfänger lassen auf jeden Typ schließen.
quelle
user
spielt eine Rolle, wenn Sie versuchen, die Funktion zu erweitern, da sie bestimmt, was Sie mit der tun könnenuser
. Dies ist wichtig, wenn Sie eine Sicherheitsüberprüfung hinzufügen möchten (z. B. aufgrund einer Sicherheitsanfälligkeit) oder vergessen haben, dass Sie mit dem Benutzer nicht nur eine Anzeige vornehmen müssen. Diese Art des Lesens zur Erweiterung ist zwar seltener als nur das Lesen des Codes, aber sie sind auch ein wesentlicher Bestandteil unserer Arbeit.Ich halte Typinferenz für sehr wichtig und sollte in jeder modernen Sprache unterstützt werden. Wir alle entwickeln uns in IDEs und sie könnten sehr hilfreich sein, falls Sie den abgeleiteten Typ kennenlernen möchten, nur wenige von uns hacken sich ein
vi
. Denken Sie zum Beispiel an Ausführlichkeit und Zeremoniencode in Java.Aber Sie können sagen, es ist in Ordnung, meine IDE wird mir helfen, es könnte ein gültiger Punkt sein. Einige Funktionen wären jedoch ohne die Hilfe von Typinferenz nicht verfügbar, z. B. anonyme C # -Typen.
Linq wäre nicht so schön, wie es jetzt ohne die Hilfe von Typinferenz ist,
Select
zum BeispielDieser anonyme Typ wird sauber in die Variable übernommen.
Ich mag keine Typinferenz bei Rückgabetypen,
Scala
da ich denke, dass Ihr Punkt hier zutrifft. Es sollte uns klar sein, was eine Funktion zurückgibt, damit wir die API flüssiger verwenden könnenquelle
Map<String,HashMap<String,String>>
? Sicher, wenn Sie nicht mit Typen, sie dann Buchstabieren hat wenig Nutzen.Table<User, File, String>
ist jedoch informativer, und es ist von Vorteil, es zu schreiben.Ich denke, die Antwort darauf ist wirklich einfach: Es erspart das Lesen und Schreiben redundanter Informationen. Besonders in objektorientierten Sprachen, in denen Sie auf beiden Seiten des Gleichheitszeichens eine Schrift haben.
Hier erfahren Sie auch, wann Sie es verwenden sollten oder nicht - wenn die Informationen nicht redundant sind.
quelle
Angenommen, man sieht den Code:
Wenn dies
someBigLongGenericType
anhand des Rückgabetyps zuweisbar istsomeFactoryMethod
, wie wahrscheinlich ist es, dass jemand, der den Code liest, dies bemerkt, wenn die Typen nicht genau übereinstimmen, und wie schnell kann jemand, der die Diskrepanz bemerkt hat, erkennen, ob es beabsichtigt war oder nicht?Indem eine Sprache Rückschlüsse zulässt, kann sie jemandem, der Code liest, vorschlagen, dass die Person versuchen sollte, einen Grund dafür zu finden, wenn der Typ einer Variablen explizit angegeben wird. Dies wiederum ermöglicht es Leuten, die Code lesen, ihre Bemühungen besser zu fokussieren. Wenn im Gegensatz dazu die überwiegende Mehrheit der Fälle, in denen ein Typ angegeben wird, genau derselbe ist, auf den geschlossen wurde, ist es möglicherweise weniger anfällig, dass jemand, der Code liest, bemerkt, dass er sich geringfügig unterscheidet .
quelle
Ich sehe, dass es bereits einige gute Antworten gibt. Einige davon wiederhole ich, aber manchmal möchten Sie die Dinge einfach in Ihre eigenen Worte fassen. Ich werde einige Beispiele aus C ++ kommentieren, da dies die Sprache ist, mit der ich am vertrautesten bin.
Was notwendig ist, ist niemals unklug. Typinferenz ist notwendig, um andere Sprachfunktionen praktisch zu machen. In C ++ ist es möglich, unaussprechbare Typen zu haben.
C ++ 11 fügte Lambdas hinzu, die auch nicht ausgesprochen werden können.
Typinferenz untermauert auch Vorlagen.
Aber Ihre Fragen lauteten: "Warum sollte ich als Programmierer den Typ meiner Variablen ableiten wollen, wenn ich den Code lese? Ist es nicht schneller für jemanden, nur den Typ zu lesen, als zu überlegen, welcher Typ vorhanden ist?"
Typinferenz beseitigt Redundanz. Wenn es darum geht, Code zu lesen, kann es manchmal schneller und einfacher sein, redundante Informationen im Code zu haben, aber Redundanz kann die nützlichen Informationen überschatten . Beispielsweise:
Ein C ++ - Programmierer ist mit der Standardbibliothek nur wenig vertraut, um zu erkennen, dass es sich bei i um einen Iterator handelt,
i = v.begin()
sodass die explizite Typdeklaration nur einen begrenzten Wert hat. Durch sein Vorhandensein verdeckt es wichtigere Details (wie das, dasi
auf den Anfang des Vektors zeigt). Die feine Antwort von @amon liefert ein noch besseres Beispiel für die Ausführlichkeit, die wichtige Details in den Schatten stellt. Im Gegensatz dazu werden bei der Verwendung von Typinferenz die wichtigen Details stärker hervorgehoben.Obwohl das Lesen von Code wichtig ist, reicht es nicht aus. Irgendwann müssen Sie aufhören zu lesen und mit dem Schreiben von neuem Code beginnen. Redundanz im Code macht das Ändern von Code langsamer und schwieriger. Angenommen, ich habe das folgende Codefragment:
In dem Fall, dass ich den Werttyp des Vektors ändern muss, um den Code zu verdoppeln, in:
In diesem Fall muss ich den Code an zwei Stellen ändern. Vergleichen Sie die Typinferenz mit dem Originalcode:
Und der geänderte Code:
Beachten Sie, dass ich nur noch eine Codezeile ändern muss. Wenn Sie dies auf ein großes Programm extrapolieren, kann die Typinferenz Änderungen an Typen viel schneller verbreiten als mit einem Editor.
Redundanz im Code schafft die Möglichkeit von Fehlern. Immer wenn Ihr Code davon abhängig ist, dass zwei Informationen gleichwertig sind, besteht die Möglichkeit eines Fehlers. Beispielsweise gibt es eine Inkonsistenz zwischen den beiden Typen in dieser Anweisung, die wahrscheinlich nicht beabsichtigt ist:
Redundanz erschwert das Erkennen von Absichten. In einigen Fällen kann die Typinferenz einfacher zu lesen und zu verstehen sein, da sie einfacher ist als die explizite Typspezifikation. Betrachten Sie das Codefragment:
In dem Fall, dass
sq(x)
ein zurückgegeben wirdint
, ist es nicht offensichtlich, oby
es sich um einenint
Rückgabetyp handeltsq(x)
oder ob er zu den verwendeten Anweisungen passty
. Wenn ich anderen Code so ändere, dass ersq(x)
nicht mehr zurückgegeben wirdint
, ist allein aus dieser Zeile nicht ersichtlich, ob der Typ vony
aktualisiert werden soll. Vergleichen Sie den gleichen Code, verwenden Sie jedoch die Typinferenz:In diesem ist die Absicht klar,
y
muss der gleiche Typ sein, wie von zurückgegebensq(x)
. Wenn der Code den Rückgabetypsq(x)
vony
ändert, wird der Änderungstyp automatisch angepasst.In C ++ gibt es einen zweiten Grund, warum das obige Beispiel mit der Typinferenz einfacher ist. Die Typinferenz kann keine implizite Typkonvertierung einführen. Wenn der Rückgabetyp
sq(x)
nicht istint
, fügt der Compiler stillschweigend eine implizite Konvertierung in einint
. Wenn der Rückgabetyp vonsq(x)
ein Typ komplexer Typ ist, der definiertoperator int()
, kann dieser verborgene Funktionsaufruf beliebig komplex sein.quelle
typeof
durch die Sprache unbrauchbar macht. Und das ist ein Defizit der Sprache selbst, das meiner Meinung nach behoben werden sollte.