Gibt es einen echten Vorteil für dynamische Sprachen? [geschlossen]

29

Zunächst möchte ich sagen, dass Java die einzige Sprache ist, die ich jemals verwendet habe. Entschuldigen Sie bitte meine Unkenntnis zu diesem Thema.

Mit dynamisch getippten Sprachen können Sie in jede Variable einen beliebigen Wert eingeben. So könnten Sie zum Beispiel die folgende Funktion schreiben (psuedocode):

void makeItBark(dog){
    dog.bark();
}

Und Sie können darin jeden Wert weitergeben. Solange der Wert eine bark()Methode hat, wird der Code ausgeführt. Andernfalls wird eine Laufzeitausnahme oder ähnliches ausgelöst. (Bitte korrigieren Sie mich, wenn ich mich irre).

Dies gibt Ihnen anscheinend Flexibilität.

Ich habe jedoch etwas über dynamische Sprachen gelesen, und was die Leute sagen, ist, dass Sie beim Entwerfen oder Schreiben von Code in einer dynamischen Sprache über Typen nachdenken und diese berücksichtigen, genauso wie Sie es in einer statisch getippten Sprache tun würden.

Wenn makeItBark()Sie beispielsweise die Funktion schreiben , möchten Sie, dass sie nur "Dinge akzeptiert, die bellen können", und Sie müssen weiterhin sicherstellen, dass Sie nur diese Art von Dingen übergeben. Der einzige Unterschied ist, dass der Compiler Ihnen jetzt nicht sagt, wann Sie einen Fehler gemacht haben.

Sicher, es gibt einen Vorteil dieses Ansatzes: Um in statischen Sprachen zu erreichen, dass diese Funktion alles akzeptiert, was bellen kann, müssen Sie eine explizite BarkerSchnittstelle implementieren . Dies scheint jedoch ein kleiner Vorteil zu sein.

Vermisse ich etwas? Was verdiene ich eigentlich mit einer dynamisch getippten Sprache?

Aviv Cohn
quelle
6
makeItBark(collections.namedtuple("Dog", "bark")(lambda x: "woof woof")). Dieses Argument ist nicht einmal eine Klasse , es ist ein anonymes Tupel mit dem Namen. Duck Typing ("wenn es wie ein ... quakt") ermöglicht es Ihnen, Ad-hoc-Schnittstellen mit im Wesentlichen null Einschränkungen und ohne syntaktischen Aufwand zu erstellen. Sie können dies in einer Sprache wie Java tun, aber Sie haben am Ende viel chaotisches Nachdenken. Wenn für eine Funktion in Java eine ArrayList erforderlich ist und Sie ihr einen anderen Auflistungstyp zuweisen möchten, sind Sie SOL. In Python kann das nicht einmal hochkommen.
Phoshi
2
Diese Art von Frage wurde schon einmal gestellt: hier , hier und hier . Insbesondere das erste Beispiel scheint Ihre Frage zu beantworten. Vielleicht können Sie Ihre umformulieren, um es deutlich zu machen?
Logc
3
Beachten Sie, dass Sie zum Beispiel in C ++ eine Template-Funktion haben können, die mit jedem Typ T mit einer bark()Methode funktioniert , wobei der Compiler sich beschwert, wenn Sie etwas Falsches übergeben, ohne tatsächlich eine Schnittstelle deklarieren zu müssen, die bark () enthält.
Wilbert
2
@Phoshi Das Argument in Python muss immer noch von einem bestimmten Typ sein - zum Beispiel kann es keine Zahl sein. Wenn Sie über eine eigene Ad-hoc-Implementierung von Objekten verfügen, bei der die Mitglieder über eine benutzerdefinierte getMemberFunktion abgerufen werden, kommt es zu einer makeItBarkExplosion, weil Sie dog.barkanstelle von aufgerufen haben dog.getMember("bark"). Der Code funktioniert nur, wenn alle implizit der Verwendung des nativen Objekttyps von Python zustimmen.
Doval
2
@Phoshi Just because I wrote makeItBark with my own types in mind doesn't mean you can't use yours, wheras in a static language it probably /does/ mean that.Wie in meiner Antwort ausgeführt, ist dies im Allgemeinen nicht der Fall . Dies ist bei Java und C # der Fall, aber diese Sprachen weisen verkrüppelte Typ- und Modulsysteme auf, sodass sie nicht repräsentativ für die Möglichkeiten der statischen Typisierung sind. Ich kann ein perfektes Generikum makeItBarkin mehreren statisch typisierten Sprachen schreiben , sogar in nicht funktionalen Sprachen wie C ++ oder D.
Doval

Antworten:

35

Dynamisch getippte Sprachen werden einheitlich getippt

Beim Vergleich von Typsystemen bietet das dynamische Schreiben keinen Vorteil. Dynamische Typisierung ist ein Sonderfall der statischen Typisierung - es ist eine Sprache mit statischer Typisierung, bei der jede Variable denselben Typ hat. Sie können das Gleiche in Java erreichen (Minuspräzision), indem Sie dafür sorgen, dass jede Variable vom Typ Objectist und dass "Objekt" -Werte vom Typ sind Map<String, Object>:

void makeItBark(Object dog) {
    Map<String, Object> dogMap = (Map<String, Object>) dog;
    Runnable bark = (Runnable) dogMap.get("bark");
    bark.run();
}

Selbst ohne Reflektion können Sie den gleichen Effekt in nahezu jeder statisch typisierten Sprache erzielen, abgesehen von der syntaktischen Bequemlichkeit. Sie erhalten keine zusätzliche Ausdruckskraft. im Gegenteil, Sie haben weniger Aussagekraft , weil in einer dynamisch typisierte Sprache, werden Sie verweigern die Möglichkeit , Variablen auf bestimmte Arten zu beschränken.

Eine Entenrinde in einer statisch typisierten Sprache bellen lassen

Darüber hinaus können Sie mit einer guten statischen Sprache Code schreiben, der mit jedem Typ mit einer barkOperation funktioniert. In Haskell ist dies eine Typklasse:

class Barkable a where
    bark :: a -> unit

Dies drückt die Einschränkung aus, dass für einen Typ a, der als Barkable betrachtet werden soll, eine barkFunktion vorhanden sein muss , die einen Wert dieses Typs annimmt und nichts zurückgibt.

Sie können dann generische Funktionen in Bezug auf die BarkableEinschränkung schreiben :

makeItBark :: Barkable a => a -> unit
makeItBark barker = bark (barker)

Dies bedeutet, dass makeItBarkes für jeden Typ geeignet ist Barkable, der die Anforderungen erfüllt. Dies mag interfaceJava oder C # ähneln, hat aber einen großen Vorteil: Typen müssen nicht im Voraus angeben , welchen Typklassen sie entsprechen. Ich kann diese Art sagen Duckist Barkablejederzeit, auch wenn Duckein Dritte Typ ich nicht schreiben. Tatsächlich spielt es keine Rolle, dass der Schreiber von Duckkeine barkFunktion geschrieben hat - ich kann sie nachträglich bereitstellen, wenn ich der Sprache sage, die Folgendes Duckerfüllt Barkable:

instance Barkable Duck where
    bark d = quack (punch (d))

makeItBark (aDuck)

Dies besagt, dass Ducks bellen können, und ihre Barkenfunktion wird implementiert, indem die Ente gestanzt wird, bevor sie quakt. Wenn das nicht im Weg ist, können wir makeItBarkEnten suchen.

Standard MLund OCamlsind noch flexibler, da Sie dieselbe Typenklasse auf mehr als eine Weise erfüllen können. In diesen Sprachen kann ich sagen, dass ganze Zahlen mit der herkömmlichen Reihenfolge geordnet werden können und sich dann umdrehen und sagen, dass sie auch durch Teilbarkeit geordnet werden können (z. B. 10 > 5weil 10 durch 5 teilbar ist). In Haskell können Sie eine Typklasse nur einmal instanziieren. (Auf diese Weise weiß Haskell automatisch, dass es in Ordnung ist, barkeine Ente anzurufen . In SML oder OCaml müssen Sie genau angeben, welche bark Funktion Sie möchten, da es möglicherweise mehr als eine geben kann.)

Prägnanz

Natürlich gibt es syntaktische Unterschiede. Der von Ihnen vorgestellte Python-Code ist weitaus prägnanter als das von mir geschriebene Java-Äquivalent. In der Praxis ist diese Prägnanz ein wesentlicher Bestandteil der Faszination dynamisch typisierter Sprachen. Aber Typinferenz ermöglicht es Ihnen , Code zu schreiben, in nur so kurz ist statisch typisierten Sprachen, indem Sie entlasten zu müssen explizit die Typen schreiben jede Variable. Eine statisch typisierte Sprache kann auch native Unterstützung für dynamisches Schreiben bieten und die Ausführlichkeit aller Casting- und Kartenmanipulationen (z. B. C # dynamic) beseitigen .

Richtige, aber falsch geschriebene Programme

Um fair zu sein, schließt statische Typisierung notwendigerweise einige technisch korrekte Programme aus, obwohl die Typprüfung dies nicht überprüfen kann. Beispielsweise:

if this_variable_is_always_true:
    return "some string"
else:
    return 6

Die meisten statisch typisierten Sprachen würden diese ifAnweisung ablehnen , obwohl die else-Verzweigung niemals auftreten wird. In der Praxis scheint niemand diese Art von Code zu verwenden - alles, was für die Typprüfung zu clever ist, wird wahrscheinlich dazu führen, dass zukünftige Betreuer Ihres Codes Sie und Ihre nächsten Verwandten verfluchen. Beispiel: Jemand hat erfolgreich 4 Open-Source-Python-Projekte in Haskell übersetzt, was bedeutet, dass er nichts getan hat, was eine gute statisch typisierte Sprache nicht kompilieren konnte. Außerdem stellte der Compiler einige typbezogene Fehler fest, die bei den Komponententests nicht festgestellt wurden.

Das stärkste Argument, das ich für dynamisches Tippen gesehen habe, sind Lisps Makros, da Sie damit die Syntax der Sprache beliebig erweitern können. Allerdings typisierten Racket ist eine statisch typisierte Dialekt von Lisp , die Makros hat, so dass es statische Typisierung scheint und Makros schließen sich nicht aus, wenn auch vielleicht härter gleichzeitig zu implementieren.

Äpfel und Orangen

Vergessen Sie nicht, dass die Unterschiede in den Sprachen größer sind als nur das Typensystem. Vor Java 8 war es praktisch unmöglich , irgendeine Art von funktionaler Programmierung in Java durchzuführen. Ein einfaches Lambda würde 4 Zeilen Boilerplate-Code für anonyme Klassen erfordern. Java unterstützt auch keine Sammlungsliterale (z [1, 2, 3]. B. ). Es kann auch Unterschiede in der Qualität und Verfügbarkeit von Tools (IDEs, Debuggern), Bibliotheken und Community-Support geben. Wenn jemand behauptet, in Python oder Ruby produktiver zu sein als in Java, muss diese Ungleichheit der Funktionen berücksichtigt werden. Es gibt einen Unterschied zwischen dem Vergleichen von Sprachen mit allen enthaltenen Batterien , Sprachkernen und Typensystemen .

Doval
quelle
2
Sie haben vergessen, Ihre Quelle für den ersten Absatz
2
@ Matt Re: 1, ich habe nicht angenommen, dass es nicht wichtig ist; Ich habe es unter Prägnanz angesprochen. Re: 2, obwohl ich nie gesagt , es ausdrücklich, von „gut“ Ich meine „gründlicher Typinferenz hat“ und „weist ein Modulsystem , das Sie Code übereinstimmen kann Signaturen geben nach der Tat “, nicht up-front wie Java / C # -Schnittstellen. Zu 3: Die Beweislast liegt bei Ihnen, wenn Sie mir erklären, wie Sie bei zwei Sprachen mit gleicher Syntax und gleichen Merkmalen, einer dynamisch getippten und einer mit vollständiger Typinferenz, nicht in der Lage wären, in beiden Sprachen Code gleicher Länge zu schreiben .
Doval
3
@MattFenwick Ich habe es bereits begründet - da zwei Sprachen die gleichen Funktionen haben, eine dynamisch und eine statisch, besteht der Hauptunterschied zwischen ihnen darin, dass Typanmerkungen vorhanden sind, und Typinferenz beseitigt dies. Alle anderen Unterschiede in der Syntax sind oberflächlich, und alle Unterschiede in den Merkmalen machen den Vergleich zu Äpfeln gegen Orangen. Sie müssen zeigen, wie falsch diese Logik ist.
Doval
1
Sie sollten einen Blick auf Boo werfen. Es ist statisch mit Typinferenz geschrieben und verfügt über Makros, mit denen die Syntax der Sprache erweitert werden kann.
Mason Wheeler
1
@Doval: Richtig. Übrigens wird die Lambda-Notation nicht ausschließlich in der funktionalen Programmierung verwendet: Soweit ich weiß, hat Smalltalk anonyme Blöcke, und Smalltalk ist so objektorientiert, wie es nur geht. Daher besteht die Lösung häufig darin, einen anonymen Codeblock mit einigen Parametern zu umgehen, unabhängig davon, ob es sich um eine anonyme Funktion oder ein anonymes Objekt mit genau einer anonymen Methode handelt. Ich denke, diese beiden Konstrukte drücken im Wesentlichen dieselbe Idee aus zwei unterschiedlichen Perspektiven aus (der funktionalen und der objektorientierten).
Giorgio
11

Dies ist ein schwieriges und recht subjektives Thema. (Und Ihre Frage wird möglicherweise als meinungsbasiert geschlossen, aber das bedeutet nicht, dass es eine schlechte Frage ist - im Gegenteil, auch wenn das Nachdenken über solche metasprachlichen Fragen ein gutes Zeichen ist -, ist es einfach nicht gut für das Q & A-Format geeignet dieses Forums.)

Hier ist meine Ansicht dazu: Der Sinn von Hochsprachen besteht darin, die Möglichkeiten eines Programmierers mit dem Computer einzuschränken . Dies ist für viele Menschen überraschend, da sie der Ansicht sind, dass es das Ziel ist, den Benutzern mehr Macht zu geben und mehr zu erreichen . Da jedoch alles, was Sie in Prolog, C ++ oder List schreiben, irgendwann als Maschinencode ausgeführt wird, ist es unmöglich, dem Programmierer mehr Leistung zu geben, als die Assemblersprache bereits bietet.

Der Sinn einer Hochsprache ist es , dem Programmierer zu helfen, den von ihm selbst erstellten Code besser zu verstehen und ihn effizienter zu machen, wenn es darum geht, dasselbe zu tun. Ein Unterprogrammname ist leichter zu merken als eine hexadezimale Adresse. Ein automatischer Argumentenzähler ist einfacher zu verwenden als eine Aufrufsequenz. Hier müssen Sie die Anzahl der Argumente ohne Hilfe exakt selbst bestimmen. Ein Typensystem geht weiter und schränkt das ein Art der Argumente ein, die Sie an einer bestimmten Stelle angeben können.

Hier unterscheidet sich die Wahrnehmung der Menschen. Einige Leute (ich gehöre dazu) sind der Meinung, dass es nützlich ist, dies im Code zu deklarieren und automatisch daran erinnert zu werden, wenn Ihre Kennwortprüfroutine ohnehin genau zwei Argumente und immer eine Zeichenfolge gefolgt von einer numerischen ID erwartet Sie vergessen später, diese Regel zu befolgen. Das Auslagern einer solchen kleinen Buchhaltung an den Compiler befreit Sie von übergeordneten Problemen und erleichtert das Entwerfen und Entwickeln Ihres Systems. Daher sind Typsysteme ein Nettogewinn: Sie lassen den Computer tun, was er kann, und Menschen tun, was sie können.

Andere sehen das ganz anders. Sie mögen es nicht, von einem Compiler gesagt zu werden, was zu tun ist. Ihnen missfällt der zusätzliche Aufwand, sich für die Typdeklaration zu entscheiden und diese zu tippen. Sie bevorzugen einen explorativen Programmierstil, bei dem Sie tatsächlichen Geschäftscode schreiben, ohne einen Plan zu haben, der Ihnen genau sagt, welche Typen und Argumente wo verwendet werden sollen. Und für die Art der Programmierung, die sie verwenden, mag dies durchaus zutreffen.

Ich vereinfache das hier natürlich schrecklich. Die Typprüfung ist nicht streng an explizite Typdeklarationen gebunden. Es gibt auch Typinferenz. Das Programmieren mit Routinen, die tatsächlich Argumente unterschiedlicher Art verwenden, ermöglicht ganz andere und sehr mächtige Dinge, die sonst unmöglich wären. Es ist nur so, dass viele Leute nicht aufmerksam und konsistent genug sind, um diesen Spielraum erfolgreich zu nutzen.

Letztendlich zeigt die Tatsache, dass so unterschiedliche Sprachen sehr beliebt sind und keine Anzeichen eines Absterbens zeigen, dass die Leute sehr unterschiedlich programmieren. Ich denke, dass es bei den Features von Programmiersprachen hauptsächlich um menschliche Faktoren geht - was den menschlichen Entscheidungsprozess besser unterstützt - und solange Menschen sehr unterschiedlich arbeiten, wird der Markt gleichzeitig sehr unterschiedliche Lösungen anbieten.

Kilian Foth
quelle
3
Danke für die Antwort. Sie sagten, dass manche Leute es nicht mögen, von einem Compiler gesagt zu werden, was zu tun ist. [..] Sie bevorzugen einen explorativen Programmierstil, bei dem Sie tatsächlichen Geschäftscode schreiben, ohne einen Plan zu haben, der Ihnen genau sagt, welche Typen und Argumente wo verwendet werden sollen. ' Das verstehe ich nicht: Programmieren ist keine musikalische Improvisation. Wenn Sie in der Musik eine falsche Note treffen, klingt dies möglicherweise cool. Wenn Sie beim Programmieren etwas an eine Funktion übergeben, die nicht vorhanden sein soll, treten höchstwahrscheinlich böse Fehler auf. (Fortsetzung im nächsten Kommentar).
Aviv Cohn
3
Ich stimme zu, aber viele Leute stimmen nicht zu. Und die Leute sind ziemlich besitzergreifend über ihre mentalen Vorurteile, zumal sie sich ihrer oft nicht bewusst sind. Aus diesem Grund degenerieren Debatten über den Programmierstil in der Regel in Argumente oder Auseinandersetzungen, und es ist selten sinnvoll, sie mit zufälligen Fremden im Internet zu beginnen.
Kilian Foth
1
Das ist der Grund, warum Menschen, die dynamische Sprachen verwenden, - meiner Lektüre nach - Typen genauso berücksichtigen wie Menschen, die statische Sprachen verwenden. Denn wenn Sie eine Funktion schreiben, soll sie Argumente einer bestimmten Art annehmen. Es spielt keine Rolle, ob der Compiler dies erzwingt oder nicht. Es kommt also auf die statische Eingabe an, die Ihnen dabei hilft, und die dynamische Eingabe nicht. In beiden Fällen muss eine Funktion eine bestimmte Art von Eingabe annehmen. Ich verstehe also nicht, was der Vorteil des dynamischen Tippens ist. Auch wenn Sie einen „explorativen Programmierstil“ bevorzugen, können Sie nicht alles, was Sie wollen, in eine Funktion übertragen.
Aviv Cohn
1
Die Leute sprechen oft über sehr unterschiedliche Arten von Projekten (insbesondere in Bezug auf die Größe). Die Geschäftslogik für eine Website ist im Vergleich zu einem vollständigen ERP-System sehr einfach. Das Risiko, dass Sie etwas falsch machen, ist geringer, und der Vorteil, dass Sie Code ganz einfach wiederverwenden können, ist relevanter. Angenommen, ich habe Code, der aus einer Datenstruktur ein PDF (oder HTML) generiert. Jetzt habe ich eine andere Datenquelle (zuerst war JSON von einer REST-API, jetzt ist es Excel-Importer). In einer Sprache wie Ruby kann es sehr einfach sein, die erste Struktur zu 'simulieren', sie zum Bellen zu bringen und den PDF-Code wiederzuverwenden.
Thorsten Müller
@Prog: Der eigentliche Vorteil dynamischer Sprachen liegt in der Beschreibung von Dingen, die mit einem statischen Typsystem wirklich schwierig sind. Eine Funktion in Python kann zum Beispiel eine Funktionsreferenz, ein Lambda, ein Funktionsobjekt oder Gott weiß was sein, und alles funktioniert gleich. Sie können ein Objekt erstellen, das ein anderes Objekt umschließt und automatisch Methoden ohne syntaktischen Aufwand auslöst. Jede Funktion verfügt im Wesentlichen über parametrisierte Typen. Dynamische Sprachen sind großartig, um Dinge schnell zu erledigen.
Phoshi
5

Mit dynamischen Sprachen geschriebener Code ist nicht an ein statisches Typsystem gekoppelt. Daher ist dieses Fehlen einer Kopplung ein Vorteil im Vergleich zu schlechten / unzureichenden statischen Systemen (obwohl es im Vergleich zu einem großen statischen System eine Wäsche oder ein Nachteil sein kann).

Darüber hinaus muss für eine dynamische Sprache ein statisches Typsystem nicht entworfen, implementiert, getestet und gewartet werden. Dies könnte die Implementierung im Vergleich zu einer Sprache mit einem statischen Typsystem vereinfachen.


quelle
2
Neigen die Leute nicht dazu, ein grundlegendes statisches Typsystem mit ihren Komponententests neu zu implementieren (wenn eine gute Testabdeckung angestrebt wird)?
Den
Auch was meinst du mit "koppeln" hier? Wie würde es sich in einer Mikrodienstarchitektur manifestieren?
Den
@Den 1) Gute Frage, ich habe jedoch das Gefühl, dass es außerhalb des Anwendungsbereichs des OP und meiner Antwort liegt. 2) Ich meine Kopplung in diesem Sinne ; Kurz gesagt, verschiedene Typsysteme erlegen dem in dieser Sprache geschriebenen Code unterschiedliche (inkompatible) Einschränkungen auf. Entschuldigung, ich kann die letzte Frage nicht beantworten - ich verstehe nicht, was das Besondere an Micro-Services in dieser Hinsicht ist.
2
@Den: Sehr guter Punkt: Ich beobachte oft, dass Komponententests, die ich in Python-Cover-Fehler schreibe, von einem Compiler in einer statisch typisierten Sprache abgefangen werden.
Giorgio
@MattFenwick: Sie haben geschrieben, dass es ein Vorteil ist, dass "... für eine dynamische Sprache ein statisches Typensystem nicht entworfen, implementiert, getestet und gewartet werden muss". und Den haben festgestellt, dass Sie Ihre Typen häufig direkt in Ihrem Code entwerfen und testen müssen. Der Aufwand wird also nicht aufgehoben, sondern vom Sprachdesign in den Anwendungscode verlagert.
Giorgio,