Dynamische Eingabe c. Statisches und dynamisches Schreiben. Dynamisch typisierte Sprachen



Dieser Artikel behandelt den Unterschied zwischen statisch typisierten und dynamisch typisierten Sprachen, erörtert die Konzepte der „starken“ und „schwachen“ Typisierung und vergleicht die Leistungsfähigkeit von Typsystemen in verschiedenen Sprachen. In letzter Zeit gab es eine klare Bewegung hin zu stärkeren und leistungsfähigeren Typsystemen in der Programmierung, daher ist es wichtig zu verstehen, was fraglich wenn es um Typen und Typisierung geht.



Ein Typ ist eine Sammlung möglicher Werte. Eine Ganzzahl kann die Werte 0, 1, 2, 3 usw. annehmen. Boolean kann wahr oder falsch sein. Sie können sich Ihren eigenen Typ ausdenken, zum Beispiel den Typ „DayFive“, bei dem die Werte „give“ und „5“ möglich sind, und sonst nichts. Es ist kein String oder eine Zahl, es ist ein neuer, separater Typ.


Statisch typisierte Sprachen schränken die Typen von Variablen ein: Eine Programmiersprache kann beispielsweise wissen, dass x eine ganze Zahl ist. In diesem Fall ist es dem Programmierer verboten x = true zu machen, es wird falscher Code sein. Der Compiler weigert sich, ihn zu kompilieren, sodass wir diesen Code nicht einmal ausführen können. Eine andere statisch typisierte Sprache hat möglicherweise andere Ausdrucksfähigkeiten, und kein populäres Typsystem kann unseren High-Five-Typ ausdrücken (aber viele können andere, anspruchsvollere Ideen ausdrücken).


Dynamisch typisierte Sprachen kennzeichnen Werte mit Typen: Die Sprache weiß, dass 1 eine ganze Zahl ist, 2 eine ganze Zahl, aber sie kann nicht wissen, dass x immer eine ganze Zahl enthält.


Die Sprachlaufzeit prüft diese Labels zu unterschiedlichen Zeiten. Wenn wir versuchen, zwei Werte hinzuzufügen, kann überprüft werden, ob es sich um Zahlen, Zeichenfolgen oder Arrays handelt. Dann addiert sie diese Werte, klebt sie zusammen oder gibt je nach Typ einen Fehler aus.

statisch typisierte Sprachen

Statische Sprachen überprüfen Typen in einem Programm zur Kompilierzeit, bevor das Programm überhaupt ausgeführt wird. Jedes Programm, in dem Typen gegen die Regeln der Sprache verstoßen, wird als schlecht formatiert betrachtet. Beispielsweise lehnen die meisten statischen Sprachen den Ausdruck "a" + 1 ab (C ist eine Ausnahme von dieser Regel). Der Compiler weiß, dass „a“ ein String und 1 eine Ganzzahl ist und dass + nur funktioniert, wenn der linke und der rechte Teil vom gleichen Typ sind. Er muss das Programm also nicht ausführen, um zu verstehen, dass ein Problem vorliegt. Jeder Ausdruck in einer statisch typisierten Sprache hat einen bestimmten Typ, der bestimmt werden kann, ohne Code auszuführen.


Viele statisch typisierte Sprachen erfordern die Angabe des Typs. Die Funktion in Java public int add(int x, int y) nimmt zwei ganze Zahlen und gibt eine dritte ganze Zahl zurück. Andere statisch typisierte Sprachen können den Typ automatisch bestimmen. Die gleiche Additionsfunktion in Haskell sieht so aus: add x y = x + y . Wir teilen der Sprache die Typen nicht mit, aber sie kann sie selbst definieren, weil sie weiß, dass + nur mit Zahlen funktioniert, also müssen x und y Zahlen sein, also nimmt die add-Funktion zwei Zahlen als Argumente.


Dies verringert nicht die "statische" Natur des Typensystems. Das Typsystem von Haskell ist berühmt dafür, statisch, streng und leistungsfähig zu sein, und an all diesen Fronten ist Haskell Java voraus.

Dynamisch typisierte Sprachen

Bei dynamisch typisierten Sprachen müssen Sie keinen Typ angeben, aber sie definieren auch keinen. Variablentypen sind nicht bekannt, bis sie beim Start bestimmte Werte haben. Zum Beispiel eine Funktion in Python


def f(x, y): gib x + y zurück

kann zwei Ganzzahlen, Spleißzeichenfolgen, Listen usw. hinzufügen, und wir können nicht genau herausfinden, was vor sich geht, bis wir das Programm ausführen. Vielleicht wird f irgendwann mit zwei Strings aufgerufen und an anderer Stelle mit zwei Zahlen. In diesem Fall enthalten x und y die Werte verschiedene Typen zu anderen Zeiten. Daher sollen Werte in dynamischen Sprachen einen Typ haben, Variablen und Funktionen jedoch nicht. Der Wert 1 ist definitiv eine ganze Zahl, aber x und y können alles sein.

Vergleich

Die meisten dynamischen Sprachen geben einen Fehler aus, wenn Typen falsch verwendet werden (JavaScript ist eine notorische Ausnahme; es versucht, einen Wert für jeden Ausdruck zurückzugeben, auch wenn es keinen Sinn ergibt). Bei der Verwendung dynamisch typisierter Sprachen kann sogar ein einfacher "a" + 1-Fehler in einer Produktionsumgebung auftreten. Statische Sprachen verhindern solche Fehler, aber natürlich hängt der Grad der Verhinderung von der Leistungsfähigkeit des Typsystems ab.


Statische und dynamische Sprachen bauen auf grundlegend unterschiedlichen Vorstellungen von Programmkorrektheit auf. In einer dynamischen Sprache ist "a" + 1 ein gültiges Programm: Der Code wird ausgeführt und es tritt ein Fehler in der Laufzeit auf. In den meisten statisch typisierten Sprachen ist jedoch der Ausdruck "a" + 1 kein Programm: Es wird nicht kompiliert und nicht ausgeführt. Es ist kein gültiger Code, genau wie ein paar zufällige Zeichen!&%^@*&%^@* ist kein gültiger Code. Dieser zusätzliche Begriff von Korrektheit und Unrichtigkeit hat in dynamischen Sprachen keine Entsprechung.

Starkes und schwaches Tippen

Die Begriffe „stark“ und „schwach“ sind sehr zweideutig. Hier sind einige Beispiele für ihre Verwendung:

    Manchmal bedeutet „stark“ „statisch“.
    Es ist einfach, aber es ist besser, den Begriff "statisch" zu verwenden, weil die meisten Leute ihn verwenden und verstehen.

    Manchmal bedeutet "stark" "keine implizite Typkonvertierung".
    Mit JavaScript können Sie beispielsweise "a" + 1 schreiben, was als "schwache Typisierung" bezeichnet werden kann. Aber fast alle Sprachen bieten ein gewisses Maß an impliziter Konvertierung, mit der Sie automatisch Ganzzahlen in Gleitkommazahlen wie 1 + 1,1 konvertieren können. In Wirklichkeit verwenden die meisten Menschen das Wort „stark“, um die Grenze zwischen akzeptabler und inakzeptabler Transformation zu definieren. Es gibt keine allgemein akzeptierte Grenze, sie sind alle ungenau und hängen von der Meinung einer bestimmten Person ab.

    Manchmal bedeutet „stark“, dass es unmöglich ist, die strengen Schreibregeln in der Sprache zu umgehen.

  • Manchmal bedeutet "stark" speichersicher.
    C ist ein Beispiel für eine speicherunsichere Sprache. Wenn xs ein Array aus vier Zahlen ist, führt C problemlos xs oder xs aus und gibt unmittelbar nach xs einen Wert aus dem Speicher zurück.

Lass uns anhalten. So erfüllen einige Sprachen diese Definitionen. Wie Sie sehen, ist nur Haskell in allen Belangen konsequent „stark“. Die meisten Sprachen sind nicht so klar.



("When as" in der Spalte "Implicit Conversions" bedeutet, dass die Unterteilung zwischen stark und schwach davon abhängt, welche Art von Conversions wir für akzeptabel halten).


Häufig beziehen sich die Begriffe „stark“ und „schwach“ auf eine nicht definierte Kombination verschiedener Definitionen oben und anderer Definitionen, die hier nicht gezeigt werden. All diese Verwirrung macht die Worte „stark“ und „schwach“ praktisch bedeutungslos. Wenn Sie diese Begriffe verwenden möchten, ist es besser zu beschreiben, was genau gemeint ist. Sie könnten beispielsweise sagen: „JavaScript gibt einen Wert zurück, wenn eine Zeichenfolge zu einer Zahl hinzugefügt wird, aber Python gibt einen Fehler zurück.“ In diesem Fall werden wir unsere Energie nicht damit verschwenden, uns auf die vielfältigen Bedeutungen des Wortes „stark“ zu einigen. Oder, noch schlimmer: Wir enden mit ungelösten Missverständnissen aufgrund von Terminologie.


In den meisten Fällen sind die Begriffe „stark“ und „schwach“ im Internet vage und schlecht definierte Meinungen bestimmter Personen. Sie werden verwendet, um eine Sprache als "schlecht" oder "gut" zu bezeichnen, und diese Meinung verwandelt sich in Fachjargon.



Starkes Tippen: Ein Typsystem, das ich liebe und mit dem ich mich wohl fühle.

Schwache Typisierung: Ein Typsystem, das mich stört oder mit dem ich nicht vertraut bin.

Allmähliches Tippen

Ist es möglich hinzuzufügen statische Typen in dynamischen Sprachen? In manchen Fällen ja. Bei anderen ist es schwierig oder unmöglich. Das offensichtlichste Problem ist mit eval und anderen ähnlichen dynamischen Sprachfunktionen. 1 + eval("2") in Python ergibt 3. Aber was ergibt 1 + eval(read_from_the_network())? Es hängt davon ab, was sich zum Zeitpunkt der Ausführung im Netzwerk befindet. Wenn wir eine Zahl bekommen, dann ist der Ausdruck richtig. Wenn eine Zeichenfolge, dann nein. Es gibt keine Möglichkeit, dies vor dem Ausführen zu wissen, daher ist es unmöglich, den Typ statisch zu analysieren.


Eine in der Praxis unbefriedigende Lösung besteht darin, den eval()-Ausdruck auf den Typ Any zu setzen, was wie Object in einigen objektorientierten Programmiersprachen oder interface() in Go ist: Es ist ein Typ, der jeden Wert erfüllt.


Werte vom Typ Any sind durch nichts eingeschränkt, daher gibt es für das Typsystem keine Möglichkeit, uns beim Evaluierungscode zu helfen. Sprachen, die sowohl eval als auch ein Typsystem haben, müssen die Typsicherheit jedes Mal aufgeben, wenn eval verwendet wird.


Einige Sprachen haben eine optionale oder schrittweise Eingabe: Sie sind standardmäßig dynamisch, ermöglichen jedoch das Hinzufügen einiger statischer Anmerkungen. Python hat kürzlich optionale Typen hinzugefügt; TypeScript ist ein Add-on zu JavaScript, das optionale Typen hat; Flow führt eine statische Analyse des guten alten JavaScript-Codes durch.


Diese Sprachen bieten einige der Vorteile der statischen Typisierung, aber sie werden niemals die absolute Garantie bieten, die wirklich statische Sprachen bieten. Einige Funktionen werden statisch typisiert und einige werden dynamisch typisiert. Der Programmierer muss den Unterschied immer kennen und sich davor hüten.

Kompilieren von statisch typisiertem Code

Wenn statisch typisierter Code kompiliert wird, wird wie bei jedem Compiler zuerst die Syntax überprüft. Dann werden die Typen überprüft. Das bedeutet, dass sich eine statische Sprache zunächst über einen Syntaxfehler und nach dessen Behebung über 100 Tippfehler beschweren kann. Das Beheben des Syntaxfehlers hat diese 100 Tippfehler nicht verursacht. Der Compiler hatte einfach keine Möglichkeit, Typfehler zu erkennen, bis die Syntax korrigiert wurde.


Compiler für statische Sprachen können normalerweise mehr generieren Schnellcode als dynamische Compiler. Wenn der Compiler beispielsweise weiß, dass die Add-Funktion Ganzzahlen akzeptiert, kann er die native ADD-Anweisung verwenden Zentralprozessor. Eine dynamische Sprache überprüft zur Laufzeit den Typ und wählt abhängig von den Typen eine von vielen Add-Funktionen aus (Hinzufügen von Ganzzahlen oder Gleitkommazahlen oder Verketten von Zeichenfolgen oder vielleicht Listen?) Oder man muss entscheiden, dass ein Fehler aufgetreten ist und die Typen nicht passen. All diese Kontrollen brauchen Zeit. Dynamische Sprachen verwenden verschiedene Optimierungstricks, wie z. B. die JIT-Kompilierung (Just-in-Time), bei der der Code zur Laufzeit neu kompiliert wird, nachdem alle erforderlichen Typinformationen erhalten wurden. Allerdings kann keine dynamische Sprache mit der Geschwindigkeit von ordentlich geschriebenem statischen Code in einer Sprache wie Rust mithalten.

Argumente für statische und dynamische Typen

Befürworter eines statischen Typsystems weisen darauf hin, dass es kein Typsystem gibt einfache Fehler kann zu Problemen in der Produktion führen. Dies ist natürlich wahr. Jeder, der eine dynamische Sprache verwendet hat, hat dies am eigenen Leib erfahren.


Befürworter dynamischer Sprachen weisen darauf hin, dass dynamische Sprachen anscheinend einfacher zu programmieren sind. Dies gilt definitiv für einige der Arten von Code, die wir von Zeit zu Zeit schreiben, wie diesen Evaluierungscode. Dies ist eine umstrittene Entscheidung für die reguläre Arbeit, und hier ist es sinnvoll, sich an das vage Wort "einfach" zu erinnern. Rich Hickey hat beim Wort „einfach“ und seiner Beziehung zum Wort „einfach“ großartige Arbeit geleistet. Nachdem Sie sich diesen Bericht angesehen haben, werden Sie verstehen, dass es nicht einfach ist, das Wort „einfach“ richtig zu verwenden. Vorsicht vor "Leichtigkeit".


Die Vor- und Nachteile statischer vs. dynamischer Typsysteme sind noch kaum verstanden, hängen aber definitiv von der Sprache und dem spezifischen zu lösenden Problem ab.


JavaScript versucht fortzufahren, selbst wenn es eine unsinnige Konvertierung bedeutet (wie "a" + 1 , was "a1" ergibt). Python hingegen versucht, konservativ zu sein und gibt häufig Fehler zurück, wie im Fall von "a" + 1 .


Es gibt verschiedene Ansätze mit unterschiedlichen Sicherheitsstufen, aber sowohl Python als auch JavaScript sind dynamisch typisierte Sprachen.



Haskell erlaubt Ihnen nicht, eine Ganzzahl und eine Gleitkommazahl ohne vorherige explizite Konvertierung hinzuzufügen. C und Haskell sind trotz dieser großen Unterschiede beide statisch typisiert.


Es gibt viele Variationen dynamischer und statischer Sprachen. Jede unqualifizierte Aussage wie „Statische Sprachen sind besser als dynamische Sprachen, wenn es um X geht“ ist fast garantiert Unsinn. Das mag auf bestimmte Sprachen zutreffen, aber dann ist es besser zu sagen „Haskell ist besser als Python, wenn es um X geht“.

Vielzahl statischer Systeme

Werfen wir einen Blick auf zwei berühmte Beispiele für statisch typisierte Sprachen: Go und Haskell. Es gibt keine generischen Typen im Typsystem von Go, Typen mit "Parametern" von anderen Typen. Beispielsweise können Sie Ihren eigenen Typ für MyList-Listen erstellen, die alle von uns benötigten Daten speichern können. Wir möchten in der Lage sein, MyList von Integers, MyList von Strings usw. ohne Änderungen zu erstellen Quelle Meine Liste. Der Compiler muss sich um die Eingabe kümmern: Wenn es eine MyList mit ganzen Zahlen gibt und wir ihr versehentlich einen String hinzufügen, muss der Compiler das Programm ablehnen.


Go wurde speziell so konzipiert, dass es unmöglich war, Typen wie MyList zu definieren. Das Beste, was Sie tun können, ist, eine MyList von "leeren Schnittstellen" zu erstellen: MyList kann Objekte enthalten, aber der Compiler kennt ihren Typ einfach nicht. Wenn wir Objekte aus MyList abrufen, müssen wir dem Compiler mitteilen, um welchen Typ es sich handelt. Wenn wir sagen "Ich hole einen String", aber der Wert ist in Wirklichkeit eine Zahl, dann gibt es einen Laufzeitfehler, wie es bei dynamischen Sprachen der Fall ist.


Go fehlen auch viele der anderen Funktionen, die in den heutigen statisch typisierten Sprachen (oder sogar einigen Systemen aus den 1970er Jahren) zu finden sind. Die Schöpfer von Go hatten ihre eigenen Gründe für diese Entscheidungen, aber die Meinungen von Außenstehenden zu diesem Thema können manchmal hart klingen.


Vergleichen wir nun mit Haskell, das über ein sehr leistungsfähiges Typsystem verfügt. Wenn der Typ auf MyList gesetzt ist, dann ist der Typ „Nummernliste“ einfach MyList Integer . Haskell verhindert, dass wir versehentlich einen String zu einer Liste hinzufügen, und stellt sicher, dass wir kein Element aus der Liste in eine String-Variable einfügen.


Haskell kann viel komplexere Ideen direkt mit Typen ausdrücken. Beispielsweise bedeutet Num a => MyList a „MyList of values ​​that are of the same number type“. Es kann eine Liste von ganzen Zahlen, Gleitkommazahlen oder sein Dezimal Zahlen mit fester Genauigkeit, aber es wird definitiv nie eine Liste von Strings sein, die zur Kompilierzeit überprüft wird.


Sie können eine add-Funktion schreiben, die mit jedem numerischen Typ funktioniert. Diese Funktion hat den Typ Num a => (a -> a -> a) . Das heisst:

  • a kann ein beliebiger numerischer Typ sein (Num a =>).
  • Die Funktion nimmt zwei Argumente vom Typ a und gibt den Typ a zurück (a -> a -> a).

Letztes Beispiel. Wenn der Funktionstyp String -> String ist, dann nimmt es einen String und gibt einen String zurück. Aber wenn es ein String -> IO String ist, dann macht es auch einige I/O. Dies kann der Zugriff auf eine Festplatte, der Zugriff auf ein Netzwerk, das Lesen von einem Terminal usw. sein.


Wenn eine Funktion einen Typ hat Nein IO, dann wissen wir, dass es keine I/O-Operationen ausführt. In einer Webanwendung können Sie beispielsweise anhand ihres Typs erkennen, ob eine Funktion die Datenbank ändert. Das können keine dynamischen und fast keine statischen Sprachen. Dies ist ein Merkmal von Sprachen mit dem leistungsstärksten Tippsystem.


In den meisten Sprachen müssten wir uns mit der Funktion und allen Funktionen befassen, die von dort aufgerufen werden, und so weiter, um etwas zu finden, das die Datenbank ändert. Es ist ein langwieriger Prozess und es ist leicht, Fehler zu machen. Und das Haskell Type System kann diese Frage einfach und zuverlässig beantworten.


Vergleichen Sie diese Leistung mit Go, das nicht in der Lage ist, die einfache Idee von MyList auszudrücken, geschweige denn „eine Funktion, die zwei Argumente akzeptiert, sowohl numerisch als auch vom gleichen Typ, und die E / A ausführt“.


Der Go-Ansatz erleichtert das Schreiben von Go-Programmierwerkzeugen (insbesondere kann die Compiler-Implementierung einfach sein). Außerdem gibt es weniger Konzepte zu lernen. Wie diese Vorteile mit erheblichen Einschränkungen verglichen werden, ist eine subjektive Angelegenheit. Es kann jedoch nicht argumentiert werden, dass Haskell schwieriger zu erlernen ist als Go, und dass das Typsystem von Haskell viel leistungsfähiger ist und dass Haskell verhindern kann, dass viel mehr Arten von Fehlern kompiliert werden.


Go und Haskell sind so unterschiedliche Sprachen, dass es irreführend sein kann, sie in dieselbe Klasse von „statischen Sprachen“ einzuordnen, obwohl der Begriff korrekt verwendet wird. In Bezug auf praktische Sicherheitsvorteile ist Go näher an dynamischen Sprachen als an Haskell.


Andererseits sind einige dynamische Sprachen sicherer als einige statische Sprachen. (Python gilt allgemein als viel sicherer als C.) Wenn es darum geht, Verallgemeinerungen über statische oder dynamische Sprachen als Gruppen anzustellen, vergessen Sie nicht die große Anzahl von Unterschieden zwischen Sprachen.

Spezifische Beispiele für Unterschiede in den Fähigkeiten von Typsystemen

In leistungsfähigeren Typsystemen können Sie Einschränkungen auf kleineren Ebenen angeben. Hier sind ein paar Beispiele, aber hängen Sie nicht daran, wenn die Syntax nicht klar ist.


In Go können Sie sagen: "Die Add-Funktion nimmt zwei Ganzzahlen und gibt eine Ganzzahl zurück":


func add(x int, y int) int (Rückgabe x + y )

In Haskell können Sie sagen: „Eine Funktion dauert irgendein numerischen Typ und gibt eine Zahl desselben Typs zurück:


f:: Num a => a -> a -> a addiere x y = x + y

In Idris können Sie sagen "die Funktion nimmt zwei ganze Zahlen" und gibt eine ganze Zahl zurück, aber das erste Argument muss kleiner als das zweite Argument sein:


add: (x: Nat) -> (y: Nat) -> (auto kleiner: LT x y) -> Nat add x y = x + y

Wenn Sie versuchen, die Funktion add 2 1 aufzurufen, bei der das erste Argument größer als das zweite ist, weist der Compiler das Programm zurück zur Kompilierzeit. Es ist unmöglich, ein Programm zu schreiben, bei dem das erste Argument größer ist als das zweite. Eine seltene Sprache hat diese Fähigkeit. In den meisten Sprachen findet diese Prüfung zur Laufzeit statt: Wir würden so etwas schreiben wie if x >= y: raise SomeError() .


In Haskell gibt es kein Äquivalent zum Typ im obigen Idris-Beispiel, und in Go gibt es weder im Haskell-Beispiel noch im Idris-Beispiel ein Äquivalent. Infolgedessen kann Idris viele Fehler verhindern, die Haskell nicht verhindern kann, und Haskell kann viele Fehler verhindern, die Go nicht bemerkt. In beiden Fällen benötigen Sie Zusatzfunktionen Schreibsysteme, die die Sprache komplexer machen.

Typsysteme einiger statischer Sprachen

Hier ist eine grobe Liste der Typsysteme einiger Sprachen, geordnet nach zunehmender Macht. Diese Liste gibt Ihnen eine allgemeine Vorstellung von der Leistungsfähigkeit der Systeme, Sie müssen sie nicht als absolute Wahrheit ansehen. Die in einer Gruppe gesammelten Sprachen können sehr unterschiedlich sein. Jedes Typensystem hat seine Macken, und die meisten davon sind sehr komplex.

  • C (1972), Los (2009): Diese Systeme sind überhaupt nicht leistungsfähig, ohne Unterstützung für generische Typen. Es ist nicht möglich, den Typ MyList auf "Liste von Ganzzahlen", "Liste von Zeichenfolgen" usw. festzulegen. Stattdessen müssen Sie eine "Liste von unsignierten Werten" erstellen. Der Programmierer muss jedes Mal, wenn eine Zeichenfolge aus der Liste abgerufen wird, manuell sagen: "Dies ist eine Liste von Zeichenfolgen", und dies kann zu einem Laufzeitfehler führen.
  • Java (1995), C# (2000): Beide Sprachen unterstützen Generika, Sie können also MyList sagen und erhalten Sie eine Liste von Zeichenfolgen, die der Compiler kennt und gegen die Typregeln erzwingen kann. Die Elemente in der Liste sind vom Typ String, der Compiler erzwingt die Regeln beim Kompilieren wie gewohnt, sodass Laufzeitfehler weniger wahrscheinlich sind.
  • Haskell (1990), Rost (2010), Swift (2014): Alle diese Sprachen verfügen über mehrere erweiterte Funktionen, darunter generische Typen, algebraische Datentypen (ADTs) und Typklassen oder ähnliches (Klassentypen, Merkmale bzw. Protokolle). Rust und Swift sind beliebter als Haskell und werden von großen Organisationen (Mozilla bzw. Apple) gefördert.
  • Agda (2007), Idris (2011): Diese Sprachen unterstützen abhängige Typen, sodass Sie Typen wie „eine Funktion, die zwei ganze Zahlen x und y verwendet, wobei y größer als x ist“ erstellen können. Sogar die Einschränkung „y ist größer als x“ wird zur Kompilierzeit erzwungen. Bei der Ausführung wird y nie kleiner oder gleich x sein, egal was passiert. sehr dünn, aber wichtige Eigenschaften Systeme können in diesen Sprachen statisch geprüft werden. Sehr wenige Programmierer studieren sie, aber sie sind sehr begeistert von diesen Sprachen.

Es gibt eine klare Bewegung in Richtung leistungsfähigerer Typsysteme, insbesondere gemessen an der Popularität von Sprachen und nicht an der bloßen Tatsache, dass Sprachen existieren. Eine bemerkenswerte Ausnahme ist Go, was erklärt, warum viele Befürworter statischer Sprachen es als Rückschritt betrachten.


Gruppe zwei (Java und C#) sind Mainstream-Sprachen, ausgereift und weit verbreitet.


Gruppe drei steht an der Schwelle zum Mainstream, mit viel Unterstützung von Mozilla (Rust) und Apple (Swift).


Gruppe vier (Idris und Agda) sind weit vom Mainstream entfernt, aber das kann sich im Laufe der Zeit ändern. Gruppe-3-Sprachen waren vor zehn Jahren noch weit vom Mainstream entfernt.

Dieser Artikel enthält das absolute Minimum an Dingen, die Sie über das Tippen wissen müssen, um das dynamische Tippen nicht als böse, Lisp als nicht typisierte Sprache und C als stark typisierte Sprache zu bezeichnen.

BEI Vollversion gelegen detaillierte Beschreibung allerlei Tippereien, gewürzt mit Codebeispielen, Links zu gängigen Programmiersprachen und anschaulichen Bildern.

Ich empfehle Ihnen, zuerst die Kurzversion des Artikels zu lesen und dann, wenn Sie möchten, die vollständige Version.

Kurzfassung

Typisierende Programmiersprachen werden normalerweise in zwei große Lager eingeteilt - getippt und untypisiert (untypisiert). Ersteres umfasst C, Python, Scala, PHP und Lua, während letzteres Assemblersprache, Forth und Brainfuck umfasst.

Da „untypisiertes Schreiben“ von Natur aus so einfach wie ein Korken ist, wird es nicht weiter in andere Typen unterteilt. Typisierte Sprachen sind jedoch in mehrere sich überschneidende Kategorien unterteilt:

  • statisch / dynamisch tippen. Static wird durch die Tatsache bestimmt, dass die endgültigen Typen von Variablen und Funktionen zur Kompilierzeit festgelegt werden. Diese. Schon der Compiler ist sich zu 100% sicher, welcher Typ wo ist. Bei der dynamischen Typisierung werden alle Typen zur Laufzeit bestimmt.

    Beispiele:
    Statisch: C, Java, C#;
    Dynamisch: Python, JavaScript, Ruby.

  • stark / schwach Typisierung (manchmal auch als streng / nicht streng bezeichnet). Starke Typisierung zeichnet sich dadurch aus, dass die Sprache das Einmischen von Ausdrücken nicht zulässt verschiedene Typen und führt keine automatischen impliziten Konvertierungen durch, z. B. können Sie eine Menge nicht von einer Zeichenfolge subtrahieren. Schwach typisierte Sprachen führen viele implizite Konvertierungen automatisch durch, auch wenn Genauigkeitsverlust oder Konvertierung mehrdeutig sein können.

    Beispiele:
    Stark: Java, Python, Haskell, Lisp;
    Schwach: C, JavaScript, Visual Basic, PHP.

  • Explizit / implizit tippen. Explizit typisierte Sprachen unterscheiden sich dadurch, dass der Typ neuer Variablen / Funktionen / ihrer Argumente explizit gesetzt werden muss. Dementsprechend verlagern Sprachen mit impliziter Typisierung diese Aufgabe auf den Compiler/Interpreter.

    Beispiele:
    Explizit: C++, D, C#
    Implizit: PHP, Lua, JavaScript

Es sollte auch beachtet werden, dass sich alle diese Kategorien überschneiden, zum Beispiel hat die C-Sprache eine statische schwache explizite Typisierung und die Python-Sprache eine dynamische starke implizite Typisierung.

Trotzdem gibt es keine Sprachen mit statischer und dynamischer Typisierung gleichzeitig. Obwohl ich nach vorne blicke, sage ich, dass ich hier liege - es gibt sie wirklich, aber dazu später mehr.

ausführliche Fassung

Wem die Kurzfassung nicht reicht, gut. Kein Wunder, dass ich ausführlich geschrieben habe? Die Hauptsache ist, dass es in der kurzen Version einfach unmöglich war, alle nützlichen und interessanten Informationen unterzubringen, und die ausführliche Version wird wahrscheinlich zu lang sein, als dass jeder sie ohne Anstrengung lesen könnte.

Untypisierte Eingabe

In nicht typisierten Programmiersprachen werden alle Entitäten nur als Folgen von Bits unterschiedlicher Länge betrachtet.

Untypisiertes Tippen ist normalerweise in Low-Level- (Assembler-Sprache, Forth) und esoterischen (Brainfuck, HQ9, Piet) Sprachen enthalten. Neben seinen Nachteilen hat es aber auch einige Vorteile.

Vorteile
  • Ermöglicht das Schreiben auf extrem niedriger Ebene, und der Compiler / Interpreter stört keine Typprüfungen. Es steht Ihnen frei, jede Operation mit jeder Art von Daten durchzuführen.
  • Der resultierende Code ist normalerweise effizienter.
  • Transparenz der Anweisungen. Bei Kenntnis der Sprache besteht normalerweise kein Zweifel, was dieser oder jener Code ist.
Mängel
  • Komplexität. Oft müssen komplexe Werte wie Listen, Strings oder Strukturen dargestellt werden. Dies kann zu Unannehmlichkeiten führen.
  • Keine Kontrollen. Alle sinnlosen Aktionen, wie das Subtrahieren eines Zeigers auf ein Array von einem Zeichen, werden als vollkommen normal angesehen, was mit subtilen Fehlern behaftet ist.
  • Niedriges Abstraktionsniveau. Das Arbeiten mit komplexen Datentypen unterscheidet sich nicht vom Arbeiten mit Zahlen, was natürlich viele Schwierigkeiten mit sich bringen wird.
Starkes typloses Tippen?
Ja, das gibt es. Beispielsweise können Sie in Assemblersprache (für die x86 / x86-64-Architektur, andere kenne ich nicht) kein Programm zusammenstellen, wenn Sie versuchen, Daten aus dem rax-Register (64 Bit) in das cx-Register (16 Bit) zu laden. .

Bewegung cx, eax ; Montagezeitfehler

Es stellt sich also heraus, dass Assembler immer noch tippen kann? Ich denke, diese Kontrollen reichen nicht aus. Und Ihre Meinung hängt natürlich nur von Ihnen ab.

Statisches und dynamisches Schreiben

Der Hauptunterschied zwischen statischer (statischer) Typisierung und dynamischer (dynamischer) Typisierung besteht darin, dass die gesamte Typprüfung zur Kompilierzeit und nicht zur Laufzeit durchgeführt wird.

Einige Leute denken vielleicht, dass die statische Typisierung zu restriktiv ist (in der Tat ist sie es, aber sie wurde mit Hilfe einiger Techniken seit langem beseitigt). Für einige spielen dynamisch typisierte Sprachen mit dem Feuer, aber welche Merkmale zeichnen sie aus? Haben beide Arten eine Chance? Wenn nicht, warum gibt es so viele statisch und dynamisch typisierte Sprachen?

Finden wir es heraus.

Vorteile der statischen Typisierung
  • Typüberprüfungen werden nur einmal zur Kompilierzeit durchgeführt. Und das bedeutet, dass wir nicht ständig herausfinden müssen, ob wir versuchen, eine Zahl durch eine Zeichenfolge zu dividieren (und entweder einen Fehler ausgeben oder eine Konvertierung durchführen).
  • Ausführungsgeschwindigkeit. Aus dem vorherigen Punkt geht hervor, dass statisch typisierte Sprachen fast immer schneller sind als dynamisch typisierte.
  • Unter einigen zusätzlichen Bedingungen können Sie potenzielle Fehler bereits in der Kompilierungsphase erkennen.
  • Beschleunigung der Entwicklung mit Unterstützung der IDE (Aussortieren von Optionen, die offensichtlich nicht für den Typ geeignet sind).
Vorteile der dynamischen Eingabe
  • Einfaches Erstellen universeller Sammlungen - Haufen von allem und jedem (selten entsteht ein solcher Bedarf, aber wenn er entsteht, hilft dynamisches Schreiben).
  • Bequemlichkeit der Beschreibung verallgemeinerter Algorithmen (z. B. Array-Sortierung, die nicht nur auf einer Liste von ganzen Zahlen, sondern auch auf einer Liste von reellen Zahlen und sogar auf einer Liste von Zeichenfolgen funktioniert).
  • Einfach zu erlernen - Dynamisch typisierte Sprachen eignen sich normalerweise sehr gut für den Einstieg in die Programmierung.

Generische Programmierung
Nun, das wichtigste Argument für die dynamische Typisierung ist die Bequemlichkeit der Beschreibung generischer Algorithmen. Stellen wir uns ein Problem vor – wir brauchen eine Suchfunktion für mehrere Arrays (oder Listen) – ein Array aus ganzen Zahlen, ein Array aus reellen Zahlen und ein Array aus Zeichen.

Wie werden wir es lösen? Lassen Sie es uns in 3 verschiedenen Sprachen lösen: eine mit dynamischer Eingabe und zwei mit statischer Eingabe.

Ich nehme einen der einfachsten Suchalgorithmen - die Aufzählung. Die Funktion erhält das gesuchte Element, das Array (oder die Liste) selbst und gibt den Index des Elements zurück, oder wenn das Element nicht gefunden wird - (-1).

Dynamische Lösung (Python):
def find(required_element, list): for (index, element) in enumerate(list): if element == required_element: return index return (-1)

Wie Sie sehen, ist alles einfach und es gibt keine Probleme damit, dass die Liste gerade Zahlen, sogar Listen enthalten kann, obwohl es keine anderen Arrays gibt. Sehr gut. Gehen wir weiter - lösen Sie das gleiche Problem in C!

Statische Lösung (C):
unsigned int find_int(int benötigtes_element, int array, unsigned int size) ( for (unsigned int i = 0; i< size; ++i) if (required_element == array[i]) return i; return (-1); } unsigned int find_float(float required_element, float array, unsigned int size) { for (unsigned int i = 0; i < size; ++i) if (required_element == array[i]) return i; return (-1); } unsigned int find_char(char required_element, char array, unsigned int size) { for (unsigned int i = 0; i < size; ++i) if (required_element == array[i]) return i; return (-1); }

Nun, jede einzelne Funktion ähnelt der Python-Version, aber warum gibt es drei? Hat die statische Programmierung verloren?

Ja und nein. Es gibt mehrere Programmiertechniken, von denen wir uns nun eine ansehen werden. Es heißt Generic Programming, und die C++-Sprache unterstützt es ziemlich gut. Werfen wir einen Blick auf die neue Version:

Statische Lösung (generische Programmierung, C++):
Schablone unsigned int find(T erforderliches_element, std::vektor array) ( for (unsigned int i = 0; i< array.size(); ++i) if (required_element == array[i]) return i; return (-1); }

Gut! Sie sieht nicht viel komplizierter aus als die Python-Version und erforderte nicht viel Schreibarbeit. Darüber hinaus haben wir die Implementierung für alle Arrays erhalten, nicht nur die 3, die zur Lösung des Problems erforderlich sind!

Diese Version scheint genau das zu sein, was wir brauchen - wir erhalten sowohl die Vorteile der statischen Typisierung als auch einige der Vorteile der dynamischen.

Toll, dass es überhaupt möglich ist, aber es könnte noch besser sein. Erstens kann die generische Programmierung bequemer und schöner sein (z. B. in Haskell). Zweitens können Sie neben der generischen Programmierung auch Polymorphismus (das Ergebnis wird schlechter sein), Funktionsüberladung (ähnlich) oder Makros verwenden.

Statik in der Dynamik
Erwähnenswert ist auch, dass viele statische Sprachen eine dynamische Typisierung zulassen, zum Beispiel:
  • C# unterstützt den dynamischen Pseudotyp.
  • F# unterstützt syntaktischen Zucker in Form des ?-Operators, der verwendet werden kann, um die dynamische Eingabe nachzuahmen.
  • Haskell – dynamische Typisierung wird vom Data.Dynamic-Modul bereitgestellt.
  • Delphi - durch einen speziellen Typ Variant.
Einige dynamisch typisierte Sprachen ermöglichen es Ihnen auch, die Vorteile der statischen Typisierung zu nutzen:
  • Common Lisp - Typdeklarationen.
  • Perl - ab Version 5.6 eher eingeschränkt.
Also lass uns weitermachen?

Starkes und schwaches Tippen

Stark typisierte Sprachen erlauben kein Mischen von Entitäten verschiedener Typen in Ausdrücken und führen keine automatischen Konvertierungen durch. Sie werden auch als „stark typisierte Sprachen“ bezeichnet. Der englische Begriff dafür ist Strong Typing.

Im Gegensatz dazu ermutigen schwach typisierte Sprachen den Programmierer, verschiedene Typen in einem Ausdruck zu mischen, und der Compiler selbst konvertiert alles in einen einzigen Typ. Sie werden auch als „schwach typisierte Sprachen“ bezeichnet. Der englische Begriff dafür ist Weak Typing.

Schwaches Tippen wird oft mit dynamischem Tippen verwechselt, was völlig falsch ist. Eine dynamisch typisierte Sprache kann sowohl schwach als auch stark typisiert sein.

Allerdings legen die wenigsten Wert auf die Strenge beim Tippen. Es wird oft behauptet, dass eine statisch typisierte Sprache viele potenzielle Kompilierungsfehler abfangen kann. Sie lügen dich an!

Die Sprache muss auch eine starke Typisierung haben. In der Tat, wenn der Compiler, anstatt einen Fehler zu melden, einfach eine Zeichenfolge zu einer Zahl hinzufügt oder, noch schlimmer, eine weitere von einem Array subtrahiert, was nützt es uns dann, dass alle „Überprüfungen“ von Typen in der Kompilierungsphase stattfinden? Richtig - schwaches statisches Tippen ist noch schlimmer als starkes dynamisches Tippen! (Nun, das ist meine Meinung)

Warum also hat schwaches Tippen überhaupt keine Vorteile? Es mag so aussehen, aber trotz der Tatsache, dass ich ein starker Befürworter der starken Typisierung bin, muss ich zustimmen, dass die schwache Typisierung auch Vorteile hat.

Willst du wissen welche?

Vorteile der starken Typisierung
  • Zuverlässigkeit - Sie erhalten eine Ausnahme oder einen Kompilierungsfehler, anstatt sich schlecht zu benehmen.
  • Geschwindigkeit – statt impliziter Konvertierungen, die ziemlich teuer werden können, muss man sie bei starker Typisierung explizit schreiben, was dem Programmierer zumindest bewusst macht, dass dieses Stück Code langsam sein kann.
  • Verstehen, wie das Programm funktioniert – auch hier schreibt der Programmierer statt impliziter Typumwandlung alles selbst, was bedeutet, dass er ungefähr versteht, dass der Vergleich einer Zeichenfolge und einer Zahl nicht von selbst und nicht durch Zauberei erfolgt.
  • Gewissheit - Wenn Sie Transformationen von Hand schreiben, wissen Sie genau, was Sie transformieren und in was. Sie werden auch immer verstehen, dass solche Konvertierungen zu Genauigkeitsverlusten und falschen Ergebnissen führen können.
Vorteile schwacher Typisierung
  • Komfort bei der Verwendung gemischter Ausdrücke (z. B. aus Ganzzahlen und reellen Zahlen).
  • Vom Tippen abstrahieren und sich auf die Aufgabe konzentrieren.
  • Die Kürze des Rekords.
Okay, wir haben es herausgefunden, es stellt sich heraus, dass schwaches Tippen auch Vorteile hat! Gibt es Möglichkeiten, die Vorteile der schwachen Typisierung auf die starke Typisierung zu übertragen?

Es stellt sich heraus, dass es sogar zwei sind.

Implizites Typcasting, in eindeutigen Situationen und ohne Datenverlust
Wow… Ziemlich langer Absatz. Lassen Sie mich es weiter abkürzen zu "eingeschränkte implizite Konvertierung". Was bedeutet also die eindeutige Situation und der Datenverlust?

Eine eindeutige Situation ist eine Transformation oder Operation, bei der das Wesentliche sofort klar ist. Beispielsweise ist die Addition von zwei Zahlen eine eindeutige Situation. Aber das Konvertieren einer Zahl in ein Array ist es nicht (vielleicht wird ein Array mit einem Element erstellt, vielleicht ein Array mit einer solchen Länge, das standardmäßig mit Elementen gefüllt wird, und vielleicht wird eine Zahl in eine Zeichenfolge und dann in ein Array konvertiert von Zeichen).

Datenverlust ist noch einfacher. Wenn wir die reelle Zahl 3,5 in eine Ganzzahl umwandeln, gehen einige Daten verloren (tatsächlich ist diese Operation auch mehrdeutig - wie wird gerundet? Aufwärts? Abwärts? Weglassen des Bruchteils?).

Konvertierungen in unklaren Situationen und Konvertierungen mit Datenverlust sind sehr, sehr schlecht. Beim Programmieren gibt es nichts Schlimmeres.

Wenn Sie mir nicht glauben, lernen Sie die PL/I-Sprache oder schlagen Sie einfach ihre Spezifikation nach. Es hat Konvertierungsregeln zwischen ALLEN Datentypen! Es ist einfach die Hölle!

Okay, erinnern wir uns an die begrenzte implizite Konvertierung. Gibt es solche Sprachen? Ja, zum Beispiel in Pascal können Sie eine ganze Zahl in eine reelle Zahl umwandeln, aber nicht umgekehrt. Ähnliche Mechanismen gibt es auch in C#, Groovy und Common Lisp.

Okay, ich habe gesagt, dass es einen anderen Weg gibt, ein paar Vorteile des schwachen Tippens in einer starken Sprache zu erzielen. Und ja, es existiert und heißt Konstruktorpolymorphismus.

Ich erkläre es am Beispiel der wunderbaren Sprache Haskell.

Polymorphe Konstruktoren entstanden aus der Beobachtung, dass sichere implizite Konvertierungen am häufigsten benötigt werden, wenn numerische Literale verwendet werden.

Beispielsweise möchten Sie im Ausdruck pi + 1 nicht pi + 1.0 oder pi + float(1) schreiben. Ich möchte nur pi + 1 schreiben!

Und dies geschieht in Haskell dank der Tatsache, dass das Literal 1 keinen konkreten Typ hat. Es ist weder vollständig, noch real, noch komplex. Es ist nur eine Zahl!

Wenn wir also eine einfache Funktion sum x y schreiben, die alle Zahlen von x bis y multipliziert (mit einem Inkrement von 1), erhalten wir mehrere Versionen auf einmal – sum für ganze Zahlen, sum für reelle Zahlen, sum für rationale Zahlen, sum für komplexe Zahlen , und sogar sum für alle numerischen Typen, die Sie selbst definiert haben.

Natürlich spart diese Technik nur bei gemischten Ausdrücken mit numerischen Literalen, und das ist nur die Spitze des Eisbergs.

Daher können wir sagen, dass der beste Ausweg darin besteht, am Rande zwischen starker und schwacher Eingabe zu balancieren. Aber bisher hat keine Sprache eine perfekte Balance, daher tendiere ich eher zu stark typisierten Sprachen (wie Haskell, Java, C#, Python) als zu schwach typisierten Sprachen (wie C, JavaScript, Lua, PHP). .

Explizite und implizite Typisierung

Eine explizit typisierte Sprache erfordert, dass der Programmierer die Typen aller Variablen und Funktionen angibt, die er deklariert. Der englische Begriff dafür ist Explicit Typing.

Andererseits lädt eine implizit typisierte Sprache dazu ein, Typen zu vergessen und die Aufgabe der Typinferenz dem Compiler oder Interpreter zu überlassen. Der englische Begriff dafür ist implicit typing.

Zunächst könnte man meinen, dass implizite Typisierung mit dynamischer Typisierung und explizite Typisierung mit statischer Typisierung äquivalent ist, aber wir werden später sehen, dass dies nicht der Fall ist.

Gibt es Vorteile für jede Art, und gibt es Kombinationen davon und gibt es Sprachen, die beide Methoden unterstützen?

Vorteile der expliziten Typisierung
  • Eine Signatur für jede Funktion (z. B. int add(int, int)) macht es einfach zu bestimmen, was die Funktion macht.
  • Der Programmierer schreibt sofort auf, welche Art von Werten in einer bestimmten Variablen gespeichert werden können, wodurch die Notwendigkeit entfällt, sich dies zu merken.
Vorteile der impliziten Typisierung
  • Kurzschrift - def add(x, y) ist deutlich kürzer als int add(int x, int y) .
  • Resilienz gegenüber Veränderungen. Wenn beispielsweise in einer Funktion die temporäre Variable vom gleichen Typ wie das Eingabeargument war, dann muss in einer explizit typisierten Sprache, wenn sich der Typ des Eingabearguments ändert, auch der Typ der temporären Variablen geändert werden.
Nun, es sieht so aus, als hätten beide Ansätze Vor- und Nachteile (und wer hätte etwas anderes erwartet?), also lassen Sie uns nach Möglichkeiten suchen, diese beiden Ansätze zu kombinieren!
Explizite Eingabe nach Wahl
Es gibt standardmäßig Sprachen mit impliziter Typisierung und der Möglichkeit, den Typ der Werte bei Bedarf anzugeben. Der Compiler leitet den wahren Typ des Ausdrucks automatisch ab. Eine solche Sprache ist Haskell, lassen Sie mich Ihnen ein einfaches Beispiel zur Veranschaulichung geben:
-- Kein expliziter Typ add (x, y) = x + y -- Expliziter Typ add:: (Integer, Integer) -> Integer add (x, y) = x + y

Hinweis: Ich habe absichtlich eine uncurried-Funktion verwendet und auch absichtlich eine private Signatur anstelle des allgemeineren add:: (Num a) => a -> a -> a * geschrieben, weil Ich wollte die Idee zeigen, ohne die Syntax von Haskell zu erklären.

Obligatorische Bedingungen

Starke Typisierung impliziert die folgenden Voraussetzungen:

  1. Jedes Datenobjekt (Variable, Konstante, Ausdruck) in der Sprache hat immer einen fest definierten Typ, der zum Zeitpunkt der Programmkompilierung festgelegt (statische Typisierung) oder zur Laufzeit bestimmt wird (dynamische Typisierung).
  2. Sie können einer Variablen nur einen Wert zuweisen, der genau den gleichen Datentyp wie die Variable hat, die gleichen Einschränkungen gelten für die Übergabe von Parametern und die Rückgabe von Funktionsergebnissen.
  3. Jede Operation erfordert Parameter streng definierten Typs.
  4. Eine implizite Typkonvertierung ist nicht zulässig (d. h. der Compiler behandelt jeden Versuch, einen Wert eines anderen Typs als den für eine Variable, einen Parameter, eine Funktion oder einen Vorgang deklarierten zu verwenden, als Syntaxfehler).

Bei strikter Einhaltung der Anforderungen der starken Typisierung sind sogar Datentypen, die in der Zusammensetzung von Werten und gültigen Operationen identisch sind, inkompatibel. Wenn es in einem Programm notwendig ist, einen Wert eines Datentyps einer Variablen eines anderen Typs zuzuweisen, ist dies möglich, jedoch nur durch explizite Anwendung einer speziellen Typkonvertierungsoperation, die in solchen Fällen normalerweise Teil der Programmiersprache ist ( obwohl es formal nicht so sein muss, aber von Standardbibliotheken bereitgestellt wird).

Eintippen in Programmiersprachen

Verknüpfungen

siehe auch


Wikimedia-Stiftung. 2010 .

Sehen Sie in anderen Wörterbüchern, was "starkes Tippen" bedeutet:

    Der Datentyp ist ein grundlegendes Konzept der Programmiertheorie. Ein Datentyp definiert eine Reihe von Werten, eine Reihe von Operationen, die auf diese Werte angewendet werden können, und möglicherweise eine Möglichkeit, die Speicherung von Werten zu implementieren und Operationen auszuführen. Jede ... ... Wikipedia

    Datentypisierung Typsicherheit Typinferenz Dynamische Typisierung Statische Typisierung Starke Typisierung Weiche Typisierung Abhängige Typen Ententypisierung Hauptartikel: Starke Typisierung Dynamische Typisierungstechnik, weit verbreitet ... ... Wikipedia

    Datentypisierung Typsicherheit Typinferenz Dynamische Typisierung Statische Typisierung Starke Typisierung Weiche Typisierung Abhängige Typen Ententypisierung Hauptartikel: Starke Typisierung Statische Typisierungstechnik, weit verbreitet ... ... Wikipedia

    Die dynamische Typisierung ist eine in Programmiersprachen und Spezifikationssprachen weit verbreitete Technik, bei der eine Variable einem Typ zum Zeitpunkt der Wertzuweisung und nicht zum Zeitpunkt der Deklaration der Variablen zugeordnet wird. So in verschiedenen Bereichen ... Wikipedia

    Datentypisierung Typsicherheit Typinferenz Dynamische Typisierung Statische Typisierung Starke Typisierung Weiche Typisierung Abhängige Typen Ententypisierung Typinferenz in der Programmiercompilerfunktion ... ... Wikipedia

    Datentypisierung Typsicherheit Typinferenz Dynamische Typisierung Statische Typisierung Starke Typisierung Weiche Typisierung Abhängige Typen Ententypisierung Abhängiger Typ, in der Informatik und Logik ein Typ, der von einem Wert abhängt. Abhängig ... ... Wikipedia

    - (es gibt auch den Begriff Datentyp) ein grundlegendes Konzept der Programmiertheorie. Ein Datentyp definiert eine Reihe von Werten, eine Reihe von Operationen, die auf solche Werte angewendet werden können, und möglicherweise eine Möglichkeit, die Speicherung von Werten zu implementieren, und ... ... Wikipedia

    Datentyp Inhalt 1 Geschichte 2 Definition 3 Die Notwendigkeit, Datentypen zu verwenden ... Wikipedia

    Dieser Begriff hat andere Bedeutungen, siehe ML (Bedeutungen). ML Semantik: Multiparadigma: funktional, imperativ, modular Erschienen in: 1973 Autor(en): Robin Milner ua Edinburgh University ... Wikipedia

Alles ist sehr einfach. Es ist wie der Unterschied zwischen einem Hotel und einer privaten Wohnung.

In der Wohnung wohnt nur, wer dort gemeldet ist. Wenn beispielsweise die Familie Sidorov darin lebt, kann die Familie Pupkin für ihr ganzes Leben nicht dort leben. Gleichzeitig kann Petya Sidorov in dieser Wohnung wohnen, dann kann Grisha Sidorov dorthin ziehen (manchmal können sie sogar gleichzeitig dort wohnen - das ist ein Array). Dies ist statische Typisierung.

Die Familie Sidorov kann eine Zeit lang im Hotel wohnen. Sie müssen sich dort nicht einmal anmelden. Dann werden sie dort weggehen und die Pupkins werden dorthin ziehen. Und dann die Kusnezows. Und dann jemand anderes. Dies ist dynamisches Schreiben.

Wenn wir zur Programmierung zurückkehren, tritt der erste Fall (statische Typisierung) beispielsweise in C, C++, C#, Java und anderen auf. Bevor Sie einer Variablen zum ersten Mal einen Wert zuweisen, müssen Sie angeben, was Sie dort speichern werden: Ganzzahlen, Fließkommazahlen, Zeichenfolgen usw. ( die Sidorovs werden in dieser Wohnung wohnen). Eine dynamische Typisierung ist dagegen nicht erforderlich. Wenn Sie einen Wert zuweisen, weisen Sie gleichzeitig einer Variablen ihren Typ zu ( Vasya Pupkin von der Familie Pupkin lebt jetzt in diesem Hotelzimmer). Dies findet sich in Sprachen wie PHP, Python und JavaScript.

Beide Vorgehensweisen haben ihre Vor- und Nachteile. Welche besser oder schlechter ist, hängt von den zu lösenden Aufgaben ab. Weitere Details finden Sie beispielsweise auf Wikipedia.

Bei der statischen Typisierung kennen Sie den Typ der Variablen zum Zeitpunkt der Programmerstellung und Entwicklung des Algorithmus genau und berücksichtigen dies. diese. Wenn Sie gesagt haben, dass die Variable G eine 4-Byte-Ganzzahl ohne Vorzeichen ist, dann ist sie im Algorithmus immer genau eine 4-Byte-Ganzzahl ohne Vorzeichen (wenn überhaupt, dann müssen Sie sie explizit konvertieren oder wissen, wie der Übersetzer sie in a konvertiert bestimmte Kreise von Situationen, aber im Grunde genommen ist dies ein Algorithmusfehler, wenn es eine Typabweichung gibt, und der Compiler wird Sie zumindest warnen), mit static können Sie die Zeichenfolge "Vasya the stupid" nicht in die Zahl einfügen und zusätzliche Kontrollen bevor Sie die Variable dafür verwenden, dass "gibt es eine Zahl" - nicht erforderlich sind, verbringen Sie alle die Korrektheit der Daten zum Zeitpunkt ihrer Eingabe in das Programm oder wie vom Algorithmus selbst gefordert.

bei dynamischer Typisierung der Typ derselben Variablen in Allgemeiner Fall Ihnen nicht bekannt ist und sich bereits während der Ausführung des Programms ändern kann, und Sie dies berücksichtigen, wird Sie niemand vor einem möglichen Algorithmusfehler aufgrund eines Typenkonflikts warnen (Sie gingen bei der Entwicklung des Algorithmus davon aus, dass G eine enthält Ganzzahl, und der Benutzer hat beispielsweise eine Fließkommazahl oder schlimmer noch eine Zeile oder, sagen wir, danach eingegeben Arithmetische Operation dort ist es statt einer ganzen Zahl eine Fließkommazahl geworden, und im nächsten Schritt werden Sie versuchen, Bitoperationen zu verwenden ...), andererseits müssen Sie sich nicht mit vielen Kleinigkeiten herumschlagen Dinge.

Dieser Artikel behandelt den Unterschied zwischen statisch typisierten und dynamisch typisierten Sprachen, erörtert die Konzepte der „starken“ und „schwachen“ Typisierung und vergleicht die Leistungsfähigkeit von Typsystemen in verschiedenen Sprachen. In letzter Zeit gab es beim Programmieren eine klare Bewegung hin zu stärkeren und leistungsfähigeren Tippsystemen, daher ist es wichtig zu verstehen, was gesagt wird, wenn man über Typen und Typisierung spricht.



Ein Typ ist eine Sammlung möglicher Werte. Eine Ganzzahl kann die Werte 0, 1, 2, 3 usw. annehmen. Boolean kann wahr oder falsch sein. Sie können sich Ihren eigenen Typ ausdenken, zum Beispiel den Typ „DayFive“, bei dem die Werte „give“ und „5“ möglich sind, und sonst nichts. Es ist kein String oder eine Zahl, es ist ein neuer, separater Typ.


Statisch typisierte Sprachen schränken die Typen von Variablen ein: Eine Programmiersprache kann beispielsweise wissen, dass x eine ganze Zahl ist. In diesem Fall ist es dem Programmierer verboten x = true zu machen, es wird falscher Code sein. Der Compiler weigert sich, ihn zu kompilieren, sodass wir diesen Code nicht einmal ausführen können. Eine andere statisch typisierte Sprache hat möglicherweise andere Ausdrucksfähigkeiten, und kein populäres Typsystem kann unseren High-Five-Typ ausdrücken (aber viele können andere, anspruchsvollere Ideen ausdrücken).


Dynamisch typisierte Sprachen kennzeichnen Werte mit Typen: Die Sprache weiß, dass 1 eine ganze Zahl ist, 2 eine ganze Zahl, aber sie kann nicht wissen, dass x immer eine ganze Zahl enthält.


Die Sprachlaufzeit prüft diese Labels zu unterschiedlichen Zeiten. Wenn wir versuchen, zwei Werte hinzuzufügen, kann überprüft werden, ob es sich um Zahlen, Zeichenfolgen oder Arrays handelt. Dann addiert sie diese Werte, klebt sie zusammen oder gibt je nach Typ einen Fehler aus.

statisch typisierte Sprachen

Statische Sprachen überprüfen Typen in einem Programm zur Kompilierzeit, bevor das Programm überhaupt ausgeführt wird. Jedes Programm, in dem Typen gegen die Regeln der Sprache verstoßen, wird als schlecht formatiert betrachtet. Beispielsweise lehnen die meisten statischen Sprachen den Ausdruck "a" + 1 ab (C ist eine Ausnahme von dieser Regel). Der Compiler weiß, dass „a“ ein String und 1 eine Ganzzahl ist und dass + nur funktioniert, wenn der linke und der rechte Teil vom gleichen Typ sind. Er muss das Programm also nicht ausführen, um zu verstehen, dass ein Problem vorliegt. Jeder Ausdruck in einer statisch typisierten Sprache hat einen bestimmten Typ, der bestimmt werden kann, ohne Code auszuführen.


Viele statisch typisierte Sprachen erfordern die Angabe des Typs. Die Funktion in Java public int add(int x, int y) nimmt zwei ganze Zahlen und gibt eine dritte ganze Zahl zurück. Andere statisch typisierte Sprachen können den Typ automatisch bestimmen. Die gleiche Additionsfunktion in Haskell sieht so aus: add x y = x + y . Wir teilen der Sprache die Typen nicht mit, aber sie kann sie selbst definieren, weil sie weiß, dass + nur mit Zahlen funktioniert, also müssen x und y Zahlen sein, also nimmt die add-Funktion zwei Zahlen als Argumente.


Dies verringert nicht die "statische" Natur des Typensystems. Das Typsystem von Haskell ist berühmt dafür, statisch, streng und leistungsfähig zu sein, und an all diesen Fronten ist Haskell Java voraus.

Dynamisch typisierte Sprachen

Bei dynamisch typisierten Sprachen müssen Sie keinen Typ angeben, aber sie definieren auch keinen. Variablentypen sind nicht bekannt, bis sie beim Start bestimmte Werte haben. Zum Beispiel eine Funktion in Python


def f(x, y): gib x + y zurück

kann zwei Ganzzahlen, Spleißzeichenfolgen, Listen usw. hinzufügen, und wir können nicht genau herausfinden, was vor sich geht, bis wir das Programm ausführen. Vielleicht wird f irgendwann mit zwei Strings aufgerufen und an anderer Stelle mit zwei Zahlen. In diesem Fall enthalten x und y zu unterschiedlichen Zeiten Werte unterschiedlichen Typs. Daher sollen Werte in dynamischen Sprachen einen Typ haben, Variablen und Funktionen jedoch nicht. Der Wert 1 ist definitiv eine ganze Zahl, aber x und y können alles sein.

Vergleich

Die meisten dynamischen Sprachen geben einen Fehler aus, wenn Typen falsch verwendet werden (JavaScript ist eine notorische Ausnahme; es versucht, einen Wert für jeden Ausdruck zurückzugeben, auch wenn es keinen Sinn ergibt). Bei der Verwendung dynamisch typisierter Sprachen kann sogar ein einfacher "a" + 1-Fehler in einer Produktionsumgebung auftreten. Statische Sprachen verhindern solche Fehler, aber natürlich hängt der Grad der Verhinderung von der Leistungsfähigkeit des Typsystems ab.


Statische und dynamische Sprachen bauen auf grundlegend unterschiedlichen Vorstellungen von Programmkorrektheit auf. In einer dynamischen Sprache ist "a" + 1 ein gültiges Programm: Der Code wird ausgeführt und es tritt ein Fehler in der Laufzeit auf. In den meisten statisch typisierten Sprachen ist jedoch der Ausdruck "a" + 1 kein Programm: Es wird nicht kompiliert und nicht ausgeführt. Es ist kein gültiger Code, genau wie ein paar zufällige Zeichen!&%^@*&%^@* ist kein gültiger Code. Dieser zusätzliche Begriff von Korrektheit und Unrichtigkeit hat in dynamischen Sprachen keine Entsprechung.

Starkes und schwaches Tippen

Die Begriffe „stark“ und „schwach“ sind sehr zweideutig. Hier sind einige Beispiele für ihre Verwendung:

    Manchmal bedeutet „stark“ „statisch“.
    Es ist einfach, aber es ist besser, den Begriff "statisch" zu verwenden, weil die meisten Leute ihn verwenden und verstehen.

    Manchmal bedeutet "stark" "keine implizite Typkonvertierung".
    Mit JavaScript können Sie beispielsweise "a" + 1 schreiben, was als "schwache Typisierung" bezeichnet werden kann. Aber fast alle Sprachen bieten ein gewisses Maß an impliziter Konvertierung, mit der Sie automatisch Ganzzahlen in Gleitkommazahlen wie 1 + 1,1 konvertieren können. In Wirklichkeit verwenden die meisten Menschen das Wort „stark“, um die Grenze zwischen akzeptabler und inakzeptabler Transformation zu definieren. Es gibt keine allgemein akzeptierte Grenze, sie sind alle ungenau und hängen von der Meinung einer bestimmten Person ab.

    Manchmal bedeutet „stark“, dass es unmöglich ist, die strengen Schreibregeln in der Sprache zu umgehen.

  • Manchmal bedeutet "stark" speichersicher.
    C ist ein Beispiel für eine speicherunsichere Sprache. Wenn xs ein Array aus vier Zahlen ist, führt C problemlos xs oder xs aus und gibt unmittelbar nach xs einen Wert aus dem Speicher zurück.

Lass uns anhalten. So erfüllen einige Sprachen diese Definitionen. Wie Sie sehen, ist nur Haskell in allen Belangen konsequent „stark“. Die meisten Sprachen sind nicht so klar.



("When as" in der Spalte "Implicit Conversions" bedeutet, dass die Unterteilung zwischen stark und schwach davon abhängt, welche Art von Conversions wir für akzeptabel halten).


Häufig beziehen sich die Begriffe „stark“ und „schwach“ auf eine nicht definierte Kombination verschiedener Definitionen oben und anderer Definitionen, die hier nicht gezeigt werden. All diese Verwirrung macht die Worte „stark“ und „schwach“ praktisch bedeutungslos. Wenn Sie diese Begriffe verwenden möchten, ist es besser zu beschreiben, was genau gemeint ist. Sie könnten beispielsweise sagen: „JavaScript gibt einen Wert zurück, wenn eine Zeichenfolge zu einer Zahl hinzugefügt wird, aber Python gibt einen Fehler zurück.“ In diesem Fall werden wir unsere Energie nicht damit verschwenden, uns auf die vielfältigen Bedeutungen des Wortes „stark“ zu einigen. Oder, noch schlimmer: Wir enden mit ungelösten Missverständnissen aufgrund von Terminologie.


In den meisten Fällen sind die Begriffe „stark“ und „schwach“ im Internet vage und schlecht definierte Meinungen bestimmter Personen. Sie werden verwendet, um eine Sprache als "schlecht" oder "gut" zu bezeichnen, und diese Meinung verwandelt sich in Fachjargon.



Starkes Tippen: Ein Typsystem, das ich liebe und mit dem ich mich wohl fühle.

Schwache Typisierung: Ein Typsystem, das mich stört oder mit dem ich nicht vertraut bin.

Allmähliches Tippen

Ist es möglich, statische Typen zu dynamischen Sprachen hinzuzufügen? In manchen Fällen ja. Bei anderen ist es schwierig oder unmöglich. Das offensichtlichste Problem ist mit eval und anderen ähnlichen dynamischen Sprachfunktionen. 1 + eval("2") in Python ergibt 3. Aber was ergibt 1 + eval(read_from_the_network())? Es hängt davon ab, was sich zum Zeitpunkt der Ausführung im Netzwerk befindet. Wenn wir eine Zahl bekommen, dann ist der Ausdruck richtig. Wenn eine Zeichenfolge, dann nein. Es gibt keine Möglichkeit, dies vor dem Ausführen zu wissen, daher ist es unmöglich, den Typ statisch zu analysieren.


Eine in der Praxis unbefriedigende Lösung besteht darin, den eval()-Ausdruck auf den Typ Any zu setzen, was wie Object in einigen objektorientierten Programmiersprachen oder interface() in Go ist: Es ist ein Typ, der jeden Wert erfüllt.


Werte vom Typ Any sind durch nichts eingeschränkt, daher gibt es für das Typsystem keine Möglichkeit, uns beim Evaluierungscode zu helfen. Sprachen, die sowohl eval als auch ein Typsystem haben, müssen die Typsicherheit jedes Mal aufgeben, wenn eval verwendet wird.


Einige Sprachen haben eine optionale oder schrittweise Eingabe: Sie sind standardmäßig dynamisch, ermöglichen jedoch das Hinzufügen einiger statischer Anmerkungen. Python hat kürzlich optionale Typen hinzugefügt; TypeScript ist ein Add-on zu JavaScript, das optionale Typen hat; Flow führt eine statische Analyse des guten alten JavaScript-Codes durch.


Diese Sprachen bieten einige der Vorteile der statischen Typisierung, aber sie werden niemals die absolute Garantie bieten, die wirklich statische Sprachen bieten. Einige Funktionen werden statisch typisiert und einige werden dynamisch typisiert. Der Programmierer muss den Unterschied immer kennen und sich davor hüten.

Kompilieren von statisch typisiertem Code

Wenn statisch typisierter Code kompiliert wird, wird wie bei jedem Compiler zuerst die Syntax überprüft. Dann werden die Typen überprüft. Das bedeutet, dass sich eine statische Sprache zunächst über einen Syntaxfehler und nach dessen Behebung über 100 Tippfehler beschweren kann. Das Beheben des Syntaxfehlers hat diese 100 Tippfehler nicht verursacht. Der Compiler hatte einfach keine Möglichkeit, Typfehler zu erkennen, bis die Syntax korrigiert wurde.


Compiler für statische Sprachen können normalerweise schnelleren Code generieren als Compiler für dynamische Sprachen. Wenn der Compiler beispielsweise weiß, dass die Add-Funktion ganze Zahlen akzeptiert, kann er die native ADD-Anweisung der CPU verwenden. Eine dynamische Sprache überprüft zur Laufzeit den Typ und wählt abhängig von den Typen eine von vielen Add-Funktionen aus (Hinzufügen von Ganzzahlen oder Gleitkommazahlen oder Verketten von Zeichenfolgen oder vielleicht Listen?) Oder man muss entscheiden, dass ein Fehler aufgetreten ist und die Typen nicht passen. All diese Kontrollen brauchen Zeit. Dynamische Sprachen verwenden verschiedene Optimierungstricks, wie z. B. die JIT-Kompilierung (Just-in-Time), bei der der Code zur Laufzeit neu kompiliert wird, nachdem alle erforderlichen Typinformationen erhalten wurden. Allerdings kann keine dynamische Sprache mit der Geschwindigkeit von ordentlich geschriebenem statischen Code in einer Sprache wie Rust mithalten.

Argumente für statische und dynamische Typen

Befürworter eines statischen Typsystems weisen darauf hin, dass ohne ein Typsystem einfache Fehler zu Problemen in der Produktion führen können. Dies ist natürlich wahr. Jeder, der eine dynamische Sprache verwendet hat, hat dies am eigenen Leib erfahren.


Befürworter dynamischer Sprachen weisen darauf hin, dass dynamische Sprachen anscheinend einfacher zu programmieren sind. Dies gilt definitiv für einige der Arten von Code, die wir von Zeit zu Zeit schreiben, wie diesen Evaluierungscode. Dies ist eine umstrittene Entscheidung für die reguläre Arbeit, und hier ist es sinnvoll, sich an das vage Wort "einfach" zu erinnern. Rich Hickey hat beim Wort „einfach“ und seiner Beziehung zum Wort „einfach“ großartige Arbeit geleistet. Nachdem Sie sich diesen Bericht angesehen haben, werden Sie verstehen, dass es nicht einfach ist, das Wort „einfach“ richtig zu verwenden. Vorsicht vor "Leichtigkeit".


Die Vor- und Nachteile statischer vs. dynamischer Typsysteme sind noch kaum verstanden, hängen aber definitiv von der Sprache und dem spezifischen zu lösenden Problem ab.


JavaScript versucht fortzufahren, selbst wenn es eine unsinnige Konvertierung bedeutet (wie "a" + 1 , was "a1" ergibt). Python hingegen versucht, konservativ zu sein und gibt häufig Fehler zurück, wie im Fall von "a" + 1 .


Es gibt verschiedene Ansätze mit unterschiedlichen Sicherheitsstufen, aber sowohl Python als auch JavaScript sind dynamisch typisierte Sprachen.



Haskell erlaubt Ihnen nicht, eine Ganzzahl und eine Gleitkommazahl ohne vorherige explizite Konvertierung hinzuzufügen. C und Haskell sind trotz dieser großen Unterschiede beide statisch typisiert.


Es gibt viele Variationen dynamischer und statischer Sprachen. Jede unqualifizierte Aussage wie „Statische Sprachen sind besser als dynamische Sprachen, wenn es um X geht“ ist fast garantiert Unsinn. Das mag auf bestimmte Sprachen zutreffen, aber dann ist es besser zu sagen „Haskell ist besser als Python, wenn es um X geht“.

Vielzahl statischer Systeme

Werfen wir einen Blick auf zwei berühmte Beispiele für statisch typisierte Sprachen: Go und Haskell. Es gibt keine generischen Typen im Typsystem von Go, Typen mit "Parametern" von anderen Typen. Beispielsweise können Sie Ihren eigenen Typ für MyList-Listen erstellen, die alle von uns benötigten Daten speichern können. Wir möchten in der Lage sein, MyList aus Ganzzahlen, MyList aus Strings usw. zu erstellen, ohne den Quellcode von MyList zu ändern. Der Compiler muss sich um die Eingabe kümmern: Wenn es eine MyList mit ganzen Zahlen gibt und wir ihr versehentlich einen String hinzufügen, muss der Compiler das Programm ablehnen.


Go wurde speziell so konzipiert, dass es unmöglich war, Typen wie MyList zu definieren. Das Beste, was Sie tun können, ist, eine MyList von "leeren Schnittstellen" zu erstellen: MyList kann Objekte enthalten, aber der Compiler kennt ihren Typ einfach nicht. Wenn wir Objekte aus MyList abrufen, müssen wir dem Compiler mitteilen, um welchen Typ es sich handelt. Wenn wir sagen "Ich hole einen String", aber der Wert ist in Wirklichkeit eine Zahl, dann gibt es einen Laufzeitfehler, wie es bei dynamischen Sprachen der Fall ist.


Go fehlen auch viele der anderen Funktionen, die in den heutigen statisch typisierten Sprachen (oder sogar einigen Systemen aus den 1970er Jahren) zu finden sind. Die Schöpfer von Go hatten ihre eigenen Gründe für diese Entscheidungen, aber die Meinungen von Außenstehenden zu diesem Thema können manchmal hart klingen.


Vergleichen wir nun mit Haskell, das über ein sehr leistungsfähiges Typsystem verfügt. Wenn der Typ auf MyList gesetzt ist, dann ist der Typ „Nummernliste“ einfach MyList Integer . Haskell verhindert, dass wir versehentlich einen String zu einer Liste hinzufügen, und stellt sicher, dass wir kein Element aus der Liste in eine String-Variable einfügen.


Haskell kann viel komplexere Ideen direkt mit Typen ausdrücken. Beispielsweise bedeutet Num a => MyList a „MyList of values ​​that are of the same number type“. Es könnte eine Liste von Ganzzahlen, Gleitkommazahlen oder Dezimalzahlen mit fester Genauigkeit sein, aber es wird definitiv niemals eine Liste von Zeichenfolgen sein, die zur Kompilierzeit überprüft wird.


Sie können eine add-Funktion schreiben, die mit jedem numerischen Typ funktioniert. Diese Funktion hat den Typ Num a => (a -> a -> a) . Das heisst:

  • a kann ein beliebiger numerischer Typ sein (Num a =>).
  • Die Funktion nimmt zwei Argumente vom Typ a und gibt den Typ a zurück (a -> a -> a).

Letztes Beispiel. Wenn der Funktionstyp String -> String ist, dann nimmt es einen String und gibt einen String zurück. Aber wenn es ein String -> IO String ist, dann macht es auch einige I/O. Dies kann der Zugriff auf eine Festplatte, der Zugriff auf ein Netzwerk, das Lesen von einem Terminal usw. sein.


Wenn eine Funktion einen Typ hat Nein IO, dann wissen wir, dass es keine I/O-Operationen ausführt. In einer Webanwendung können Sie beispielsweise anhand ihres Typs erkennen, ob eine Funktion die Datenbank ändert. Das können keine dynamischen und fast keine statischen Sprachen. Dies ist ein Merkmal von Sprachen mit dem leistungsstärksten Tippsystem.


In den meisten Sprachen müssten wir uns mit der Funktion und allen Funktionen befassen, die von dort aufgerufen werden, und so weiter, um etwas zu finden, das die Datenbank ändert. Es ist ein langwieriger Prozess und es ist leicht, Fehler zu machen. Und das Haskell Type System kann diese Frage einfach und zuverlässig beantworten.


Vergleichen Sie diese Leistung mit Go, das nicht in der Lage ist, die einfache Idee von MyList auszudrücken, geschweige denn „eine Funktion, die zwei Argumente akzeptiert, sowohl numerisch als auch vom gleichen Typ, und die E / A ausführt“.


Der Go-Ansatz erleichtert das Schreiben von Go-Programmierwerkzeugen (insbesondere kann die Compiler-Implementierung einfach sein). Außerdem gibt es weniger Konzepte zu lernen. Wie diese Vorteile mit erheblichen Einschränkungen verglichen werden, ist eine subjektive Angelegenheit. Es kann jedoch nicht argumentiert werden, dass Haskell schwieriger zu erlernen ist als Go, und dass das Typsystem von Haskell viel leistungsfähiger ist und dass Haskell verhindern kann, dass viel mehr Arten von Fehlern kompiliert werden.


Go und Haskell sind so unterschiedliche Sprachen, dass es irreführend sein kann, sie in dieselbe Klasse von „statischen Sprachen“ einzuordnen, obwohl der Begriff korrekt verwendet wird. In Bezug auf praktische Sicherheitsvorteile ist Go näher an dynamischen Sprachen als an Haskell.


Andererseits sind einige dynamische Sprachen sicherer als einige statische Sprachen. (Python gilt allgemein als viel sicherer als C.) Wenn es darum geht, Verallgemeinerungen über statische oder dynamische Sprachen als Gruppen anzustellen, vergessen Sie nicht die große Anzahl von Unterschieden zwischen Sprachen.

Spezifische Beispiele für Unterschiede in den Fähigkeiten von Typsystemen

In leistungsfähigeren Typsystemen können Sie Einschränkungen auf kleineren Ebenen angeben. Hier sind ein paar Beispiele, aber hängen Sie nicht daran, wenn die Syntax nicht klar ist.


In Go können Sie sagen: "Die Add-Funktion nimmt zwei Ganzzahlen und gibt eine Ganzzahl zurück":


func add(x int, y int) int (Rückgabe x + y )

In Haskell können Sie sagen: „Eine Funktion dauert irgendein numerischen Typ und gibt eine Zahl desselben Typs zurück:


f:: Num a => a -> a -> a addiere x y = x + y

In Idris können Sie sagen "die Funktion nimmt zwei ganze Zahlen" und gibt eine ganze Zahl zurück, aber das erste Argument muss kleiner als das zweite Argument sein:


add: (x: Nat) -> (y: Nat) -> (auto kleiner: LT x y) -> Nat add x y = x + y

Wenn Sie versuchen, die Funktion add 2 1 aufzurufen, bei der das erste Argument größer als das zweite ist, weist der Compiler das Programm zurück zur Kompilierzeit. Es ist unmöglich, ein Programm zu schreiben, bei dem das erste Argument größer ist als das zweite. Eine seltene Sprache hat diese Fähigkeit. In den meisten Sprachen findet diese Prüfung zur Laufzeit statt: Wir würden so etwas schreiben wie if x >= y: raise SomeError() .


In Haskell gibt es kein Äquivalent zum Typ im obigen Idris-Beispiel, und in Go gibt es weder im Haskell-Beispiel noch im Idris-Beispiel ein Äquivalent. Infolgedessen kann Idris viele Fehler verhindern, die Haskell nicht verhindern kann, und Haskell kann viele Fehler verhindern, die Go nicht bemerkt. In beiden Fällen werden zusätzliche Merkmale des Typsystems benötigt, um die Sprache komplexer zu machen.

Typsysteme einiger statischer Sprachen

Hier ist eine grobe Liste der Typsysteme einiger Sprachen, geordnet nach zunehmender Macht. Diese Liste gibt Ihnen eine allgemeine Vorstellung von der Leistungsfähigkeit der Systeme, Sie müssen sie nicht als absolute Wahrheit ansehen. Die in einer Gruppe gesammelten Sprachen können sehr unterschiedlich sein. Jedes Typensystem hat seine Macken, und die meisten davon sind sehr komplex.

  • C (1972), Los (2009): Diese Systeme sind überhaupt nicht leistungsfähig, ohne Unterstützung für generische Typen. Es ist nicht möglich, den Typ MyList auf "Liste von Ganzzahlen", "Liste von Zeichenfolgen" usw. festzulegen. Stattdessen müssen Sie eine "Liste von unsignierten Werten" erstellen. Der Programmierer muss jedes Mal, wenn eine Zeichenfolge aus der Liste abgerufen wird, manuell sagen: "Dies ist eine Liste von Zeichenfolgen", und dies kann zu einem Laufzeitfehler führen.
  • Java (1995), C# (2000): Beide Sprachen unterstützen Generika, Sie können also MyList sagen und erhalten Sie eine Liste von Zeichenfolgen, die der Compiler kennt und gegen die Typregeln erzwingen kann. Die Elemente in der Liste sind vom Typ String, der Compiler erzwingt die Regeln beim Kompilieren wie gewohnt, sodass Laufzeitfehler weniger wahrscheinlich sind.
  • Haskell (1990), Rost (2010), Swift (2014): Alle diese Sprachen verfügen über mehrere erweiterte Funktionen, darunter generische Typen, algebraische Datentypen (ADTs) und Typklassen oder ähnliches (Klassentypen, Merkmale bzw. Protokolle). Rust und Swift sind beliebter als Haskell und werden von großen Organisationen (Mozilla bzw. Apple) gefördert.
  • Agda (2007), Idris (2011): Diese Sprachen unterstützen abhängige Typen, sodass Sie Typen wie „eine Funktion, die zwei ganze Zahlen x und y verwendet, wobei y größer als x ist“ erstellen können. Sogar die Einschränkung „y ist größer als x“ wird zur Kompilierzeit erzwungen. Bei der Ausführung wird y nie kleiner oder gleich x sein, egal was passiert. In diesen Sprachen können sehr subtile, aber wichtige Systemeigenschaften statisch überprüft werden. Sehr wenige Programmierer studieren sie, aber sie sind sehr begeistert von diesen Sprachen.

Es gibt eine klare Bewegung in Richtung leistungsfähigerer Typsysteme, insbesondere gemessen an der Popularität von Sprachen und nicht an der bloßen Tatsache, dass Sprachen existieren. Eine bemerkenswerte Ausnahme ist Go, was erklärt, warum viele Befürworter statischer Sprachen es als Rückschritt betrachten.


Gruppe zwei (Java und C#) sind Mainstream-Sprachen, ausgereift und weit verbreitet.


Gruppe drei steht an der Schwelle zum Mainstream, mit viel Unterstützung von Mozilla (Rust) und Apple (Swift).


Gruppe vier (Idris und Agda) sind weit vom Mainstream entfernt, aber das kann sich im Laufe der Zeit ändern. Gruppe-3-Sprachen waren vor zehn Jahren noch weit vom Mainstream entfernt.