Ich benutze Python seit ein paar Tagen und ich denke, ich verstehe den Unterschied zwischen dynamischer und statischer Typisierung. Was ich nicht verstehe, ist, unter welchen Umständen es bevorzugt würde. Es ist flexibel und lesbar, jedoch auf Kosten von mehr Laufzeitprüfungen und zusätzlichen erforderlichen Komponententests.
Welche Gründe sprechen neben nichtfunktionalen Kriterien wie Flexibilität und Lesbarkeit für dynamisches Tippen? Was kann ich mit dynamischer Eingabe tun, die sonst nicht möglich ist? Welches konkrete Codebeispiel veranschaulicht einen konkreten Vorteil der dynamischen Typisierung?
dynamic-typing
static-typing
Justin984
quelle
quelle
Antworten:
Da Sie nach einem bestimmten Beispiel gefragt haben, gebe ich Ihnen eines.
Rob Conerys massiver ORM besteht aus 400 Codezeilen. Es ist so klein, weil Rob in der Lage ist, SQL-Tabellen abzubilden und Objektergebnisse bereitzustellen, ohne dass viele statische Typen erforderlich sind, um die SQL-Tabellen zu spiegeln. Dies wird mithilfe des
dynamic
Datentyps in C # erreicht. Robs Webseite beschreibt diesen Prozess im Detail, aber es scheint klar zu sein, dass in diesem speziellen Anwendungsfall die dynamische Typisierung zum großen Teil für die Kürze des Codes verantwortlich ist.Vergleichen Sie mit Sam Saffrons Dapper , der statische Typen verwendet. Die
SQLMapper
Klasse allein besteht aus 3000 Codezeilen.Beachten Sie, dass die üblichen Haftungsausschlüsse gelten und Ihr Kilometerstand variieren kann. Dapper hat andere Ziele als Massive. Ich zeige dies nur als Beispiel für eine Aktion, die Sie in 400 Codezeilen ausführen können, die ohne dynamisches Tippen wahrscheinlich nicht möglich wäre.
Mit der dynamischen Typisierung können Sie Ihre Typentscheidungen auf die Laufzeit verschieben. Das ist alles.
Unabhängig davon, ob Sie eine dynamisch oder statisch getippte Sprache verwenden, müssen Ihre Typwahlen dennoch sinnvoll sein. Sie werden nicht zwei Zeichenfolgen zusammenfügen und eine numerische Antwort erwarten, es sei denn, die Zeichenfolgen enthalten numerische Daten. Andernfalls erhalten Sie unerwartete Ergebnisse. Bei einer statisch getippten Sprache können Sie dies nicht in erster Linie tun.
Befürworter von Sprachen mit statischem Typ weisen darauf hin, dass der Compiler beim Kompilieren eine erhebliche Menge an "Sanity Checking" Ihres Codes durchführen kann, bevor eine einzelne Zeile ausgeführt wird. Dies ist eine gute Sache ™.
C # hat das
dynamic
Schlüsselwort, mit dem Sie die Typentscheidung auf die Laufzeit verschieben können, ohne die Vorteile der statischen Typensicherheit im Rest Ihres Codes zu verlieren. Typinferenz (var
) beseitigt den Aufwand für das Schreiben in einer statisch typisierten Sprache, indem es nicht mehr erforderlich ist, Typen immer explizit zu deklarieren.Dynamische Sprachen scheinen eine interaktivere, unmittelbarere Herangehensweise an die Programmierung zu bevorzugen. Niemand erwartet, dass Sie eine Klasse schreiben und einen Kompilierungszyklus durchlaufen müssen, um ein bisschen Lisp-Code einzutippen und die Ausführung zu beobachten. Genau das soll ich aber in C # tun.
quelle
Ausdrücke wie "statisches Tippen" und "dynamisches Tippen" werden häufig verwendet, und die Leute tendieren dazu, subtil unterschiedliche Definitionen zu verwenden. Beginnen wir also damit, zu klären, was wir meinen.
Stellen Sie sich eine Sprache mit statischen Typen vor, die zur Kompilierungszeit überprüft werden. Nehmen wir jedoch an, dass ein Typfehler nur eine nicht schwerwiegende Warnung generiert und dass zur Laufzeit alles vom Typ "Ente" ist. Diese statischen Typen dienen nur dem Komfort des Programmierers und wirken sich nicht auf das Codegen aus. Dies zeigt, dass die statische Typisierung für sich genommen keine Einschränkungen mit sich bringt und sich bei der dynamischen Typisierung nicht gegenseitig ausschließt. (Objective-C ist sehr ähnlich.)
Die meisten statischen Systeme verhalten sich jedoch nicht so. Es gibt zwei allgemeine Eigenschaften von statischen Typsystemen, die Einschränkungen auferlegen können:
Der Compiler kann ein Programm ablehnen, das einen statischen Typfehler enthält.
Dies ist eine Einschränkung, da viele typsichere Programme notwendigerweise einen statischen Typfehler enthalten.
Ich habe beispielsweise ein Python-Skript, das sowohl als Python 2 als auch als Python 3 ausgeführt werden muss. Einige Funktionen haben ihre Parametertypen zwischen Python 2 und 3 geändert, sodass ich folgenden Code habe:
Eine statische Python 2-Typprüfung würde den Python 3-Code (und umgekehrt) ablehnen, obwohl er niemals ausgeführt würde. Mein typsicheres Programm enthält einen statischen Typfehler.
Ein weiteres Beispiel ist ein Mac-Programm, das unter OS X 10.6 ausgeführt werden soll, jedoch die neuen Funktionen in 10.7 nutzt. Die 10.7-Methoden können zur Laufzeit vorhanden sein oder auch nicht, und es liegt an mir, dem Programmierer, sie zu erkennen. Eine statische Typprüfung muss entweder mein Programm ablehnen, um die Typensicherheit zu gewährleisten, oder das Programm akzeptieren, zusammen mit der Möglichkeit, zur Laufzeit einen Typfehler (fehlende Funktion) zu erzeugen.
Bei der statischen Typprüfung wird davon ausgegangen, dass die Laufzeitumgebung durch die Informationen zur Kompilierungszeit angemessen beschrieben wird. Aber die Zukunft vorherzusagen ist gefährlich!
Hier ist noch eine Einschränkung:
Der Compiler generiert möglicherweise Code, der davon ausgeht, dass der Laufzeittyp der statische Typ ist.
Unter der Annahme, dass die statischen Typen "korrekt" sind, ergeben sich viele Optimierungsmöglichkeiten, die jedoch einschränkend sein können. Ein gutes Beispiel sind Proxy-Objekte, zB Remoting. Angenommen, Sie möchten ein lokales Proxy-Objekt haben, das Methodenaufrufe an ein reales Objekt in einem anderen Prozess weiterleitet. Es wäre schön, wenn der Proxy generisch wäre (so dass er sich als jedes Objekt tarnen kann) und transparent (so dass der vorhandene Code nicht wissen muss, dass er mit einem Proxy kommuniziert). Dazu kann der Compiler jedoch keinen Code generieren, der davon ausgeht, dass die statischen Typen korrekt sind, z. B. durch statische Inlining-Methodenaufrufe, da dies fehlschlägt, wenn das Objekt tatsächlich ein Proxy ist.
Beispiele für ein solches Remoting in Aktion sind NSXPCConnection von ObjC oder TransparentProxy von C # (für dessen Implementierung einige Pessimierungen in der Laufzeit erforderlich waren - siehe hier für eine Diskussion).
Wenn der Codegen nicht von den statischen Typen abhängig ist und Sie über Funktionen wie die Nachrichtenweiterleitung verfügen, können Sie mit Proxy-Objekten, dem Debuggen usw. viele interessante Aufgaben ausführen.
Das ist also eine Auswahl von Dingen, die Sie tun können, wenn Sie keinen Typprüfer benötigen. Die Einschränkungen werden nicht durch statische Typen auferlegt, sondern durch erzwungene statische Typprüfung.
quelle
A Python 2 static type checker would reject the Python 3 code (and vice versa), even though it would never be executed. My type safe program contains a static type error.
In jeder vernünftigen statischen Sprache können Sie dies mit einerIFDEF
Typ-Präprozessor-Anweisung tun , wobei in beiden Fällen die Typensicherheit gewahrt bleibt.Ententypisierte Variablen sind das erste, woran jeder denkt, aber in den meisten Fällen können Sie dieselben Vorteile durch statische Typinferenz erzielen.
In dynamisch erstellten Sammlungen zu tippen, ist jedoch auf andere Weise schwierig:
Welcher Typ kehrt also
JSON.parse
zurück? Ein Wörterbuch mit Arrays von ganzen Zahlen oder Wörterbüchern? Nein, auch das ist nicht allgemein genug.JSON.parse
muss eine Art "varianten Wert" zurückgeben, der rekursiv null, bool, float, string, ein Array eines dieser Typen oder ein Dictionary von string zu einem dieser Typen sein kann. Die Hauptstärken der dynamischen Typisierung sind solche Variantentypen.Bisher ist dies ein Vorteil von dynamischen Typen , nicht von dynamisch typisierten Sprachen. Eine anständige statische Sprache kann einen solchen Typ perfekt simulieren. (Und selbst "schlechte" Sprachen können sie oft simulieren, indem sie die Sicherheit unter der Haube unterbrechen und / oder eine ungeschickte Zugriffssyntax erfordern.)
Der Vorteil von dynamisch typisierten Sprachen besteht darin, dass solche Typen nicht von statischen Typ-Inferenzsystemen abgeleitet werden können. Sie müssen den Typ explizit schreiben. Aber in vielen Fällen - einschließlich dieses einmaligen - ist der Code zur Beschreibung des Typs genauso kompliziert wie der Code zum Parsen / Konstruieren der Objekte ohne Beschreibung des Typs, so dass dies immer noch nicht unbedingt ein Vorteil ist.
quelle
Da jedes fernpraktische statische Typsystem im Vergleich zu der Programmiersprache, um die es sich handelt, stark eingeschränkt ist, kann es nicht alle Invarianten ausdrücken, die der Code zur Laufzeit prüfen könnte. Um die Garantien, die ein Typensystem zu geben versucht, nicht zu umgehen, wählt es konservative und verbotene Anwendungsfälle, die diese Prüfungen bestehen würden, die jedoch (im Typensystem) nicht nachgewiesen werden können.
Ich werde ein Beispiel machen. Angenommen, Sie implementieren ein einfaches Datenmodell zur Beschreibung von Datenobjekten, Sammlungen von Daten usw., das statisch in dem Sinne geschrieben ist, dass, wenn das Modell
x
angibt, dass das Attribut des Objekttyps Foo eine Ganzzahl enthält, es immer eine Ganzzahl enthalten muss. Da dies ein Laufzeitkonstrukt ist, können Sie es nicht statisch eingeben. Angenommen, Sie speichern die in YAML-Dateien beschriebenen Daten. Sie erstellen eine Hash-Map (die später an eine YAML-Bibliothek übergeben wird), rufen dasx
Attribut ab, speichern es in der Map, rufen das andere Attribut ab, das zufällig eine Zeichenfolge ist, ... eine Sekunde warten? Was ist die Art vonthe_map[some_key]
jetzt? Nun, schießen Sie, wir wissen, dasssome_key
ist'x'
und das Ergebnis daher eine ganze Zahl sein muss, aber das Typensystem kann noch nicht einmal anfangen, darüber nachzudenken.Einige aktiv recherchierte Typsysteme funktionieren möglicherweise für dieses spezielle Beispiel, aber diese sind außerordentlich kompliziert (sowohl für Compiler-Autoren als auch für Programmierer), insbesondere für etwas so "Einfaches" (ich meine, ich habe es gerade in einem erklärt) Absatz).
Die heutige Lösung besteht natürlich darin, alles zu boxen und dann zu gießen (oder eine Reihe von überschriebenen Methoden zu haben, von denen die meisten "nicht implementierte" Ausnahmen auslösen). Dies ist jedoch nicht statisch typisiert, sondern ein Hack um das Typsystem , um die Typprüfungen zur Laufzeit durchzuführen.
quelle
Es gibt nichts, was Sie mit dynamischer Eingabe tun können, das Sie nicht mit statischer Eingabe tun können, da Sie dynamische Eingabe zusätzlich zu einer statisch eingegebenen Sprache implementieren können.
Ein kurzes Beispiel in Haskell:
Mit genügend Fällen können Sie ein beliebiges dynamisches Typsystem implementieren.
Umgekehrt können Sie auch jedes statisch typisierte Programm in ein äquivalentes dynamisches übersetzen. Natürlich würden Sie alle Gewährleistungen für die Richtigkeit während der Kompilierung verlieren, die die statisch typisierte Sprache bietet.
Bearbeiten: Ich wollte dies einfach halten, aber hier sind weitere Details zu einem Objektmodell
Eine Funktion verwendet eine Liste von Daten als Argumente, führt in ImplMonad Berechnungen mit Nebenwirkungen durch und gibt Daten zurück.
DMember
ist entweder ein Mitgliedswert oder eine Funktion.Erweiterung
Data
um Objekte und Funktionen. Objekte sind Listen benannter Mitglieder.Diese statischen Typen reichen aus, um jedes mir vertraute dynamisch typisierte Objektsystem zu implementieren.
quelle
Data
.+
Operator definiert , der zweiData
Werte zu einem anderenData
Wert kombiniert .Data
repräsentiert die Standardwerte im dynamischen Typsystem.Membranen :
Jeder Typ wird von einem Typ umschlossen, der dieselbe Schnittstelle hat, jedoch Nachrichten abfängt und Werte umschließt und auspackt, wenn sie die Membran passieren. Was ist die Art der Wrap-Funktion in Ihrer bevorzugten statisch getippten Sprache? Vielleicht hat Haskell einen Typ für diese Funktionen, aber die meisten statisch getippten Sprachen tun dies nicht, oder sie verwenden am Ende Objekt → Objekt, wodurch sie ihre Verantwortung als Typprüfer praktisch aufgeben.
quelle
class Foo a where ...
data Wrapper = forall a. Foo a => Wrapper a
String
da es ein konkreter Typ in Java ist. Smalltalk hat dieses Problem nicht, da es nicht versucht zu tippen#doesNotUnderstand
.Wie bereits erwähnt, können Sie mit dynamischer Eingabe theoretisch nicht viel anfangen, was Sie mit statischer Eingabe nicht tun könnten, wenn Sie bestimmte Mechanismen selbst implementieren würden. Die meisten Sprachen bieten die Mechanismen zur Typentspannung, um die Flexibilität von Typen wie Leerzeiger und Stammobjekttyp oder leere Schnittstelle zu unterstützen.
Die bessere Frage ist, warum dynamisches Tippen in bestimmten Situationen und Problemen geeigneter und angemessener ist.
Lassen Sie uns zunächst definieren
Entität - Ich würde eine allgemeine Vorstellung von einer Entität im Code benötigen. Es kann alles sein, von primitiven Zahlen bis hin zu komplexen Daten.
Verhalten - Nehmen wir an, unsere Entität verfügt über einen bestimmten Status und eine Reihe von Methoden, mit denen die Außenwelt die Entität auf bestimmte Reaktionen anweisen kann. Nennen wir die State + -Schnittstelle dieser Entität ihr Verhalten. Eine Entität kann mehr als ein Verhalten aufweisen, das in einer bestimmten Weise durch die Toolsprache kombiniert wird.
Definitionen von Entitäten und deren Verhalten - Jede Sprache bietet einige Abstraktionsmöglichkeiten, mit denen Sie das Verhalten (Methodensatz + interner Zustand) bestimmter Entitäten im Programm definieren können. Sie können diesen Verhalten einen Namen zuweisen und angeben, dass alle Instanzen mit diesem Verhalten von einem bestimmten Typ sind .
Dies ist wahrscheinlich etwas, das nicht so ungewohnt ist. Und wie Sie sagten, haben Sie den Unterschied verstanden, aber immer noch. Wahrscheinlich nicht vollständig und genaueste Erklärung, aber ich hoffe, dass es Spaß macht, etwas Wert zu bringen :)
Statische Typisierung - Das Verhalten aller Entitäten in Ihrem Programm wird zur Kompilierungszeit überprüft, bevor der Code ausgeführt wird. Dies bedeutet, dass Sie, wenn Sie möchten, dass Ihre Entität vom Typ Person sich wie Magier verhält, die Entität MagicianPerson definieren und ihr das Verhalten eines Magiers wie throwMagic () geben müssen. Wenn Sie in Ihrem Code, versehentlich zu gewöhnlichen Person.throwMagic () Compiler erzählen Sie
"Error >>> hell, this Person has no this behavior, dunno throwing magics, no run!".
Dynamische Eingabe - In Umgebungen mit dynamischer Eingabe werden verfügbare Verhaltensweisen von Entitäten erst überprüft, wenn Sie wirklich versuchen, etwas mit einer bestimmten Entität zu tun. Das Ausführen von Ruby-Code, der eine Person.throwMagic () auffordert, wird erst abgefangen, wenn Ihr Code wirklich da ist. Das klingt frustrierend, nicht wahr? Aber es klingt auch offenbarend. Aufgrund dieser Eigenschaft können Sie interessante Dinge tun. Nehmen wir an, Sie entwerfen ein Spiel, in dem sich alles an Magier wenden kann und Sie nicht genau wissen, wer das sein wird, bis Sie zu einem bestimmten Punkt im Code gelangen. Und dann kommt Frosch und du sagst
HeyYouConcreteInstanceOfFrog.include Magic
und von da an wird dieser Frosch ein besonderer Frosch, der magische Kräfte besitzt. Andere Frösche immer noch nicht. Sie sehen, in statischen Schreibsprachen müssten Sie diese Beziehung durch einen Standardmittelwert für die Kombination von Verhalten definieren (wie die Implementierung von Schnittstellen). In der dynamischen Eingabesprache können Sie dies zur Laufzeit tun, und niemand wird sich darum kümmern.Die meisten dynamischen Eingabesprachen verfügen über Mechanismen, um ein generisches Verhalten bereitzustellen, mit dem alle Nachrichten abgefangen werden, die an ihre Benutzeroberfläche übergeben werden. Zum Beispiel Ruby
method_missing
und PHP,__call
wenn ich mich gut erinnere. Das bedeutet, dass Sie zur Laufzeit des Programms alle Arten von interessanten Dingen ausführen und die Typentscheidung auf der Grundlage des aktuellen Programmstatus treffen können. Dies bringt Werkzeuge für die Modellierung eines Problems mit sich, die viel flexibler sind als in einer konservativen statischen Programmiersprache wie Java.quelle