Ich muss zugeben, dass ich nur Grundkenntnisse in Python habe und gerade Haskell lerne.
Ich frage mich, ob das Konzept der Typklassen in Python oder in Clojure (oder einer anderen dynamisch stark typisierten Sprache) existiert / Sinn macht?
Mit anderen Worten, wenn ich einen Funktionsnamen f
habe, wird abhängig von dem Laufzeitparameter, der f
zugeführt wird, eine andere Funktionsimplementierung aufgerufen (wie die ==
Funktion für Typen, die zu% co_de gehören) % Typklasse in Haskell). Gibt es ein solches Konzept in dynamischen Sprachen wie Clojure / Python?
Das kann man mit Multimethoden oder Protokollen in clojure oder mit einfachen Member-Funktionen (Klassenmethoden) in Python sehr gut erreichen. Es gibt jedoch ein wichtiges Merkmal, das in jedem von ihnen fehlt, das in Haskell vorhanden ist: polymorphie vom Rückgabetyp.
Der Compiler weiß, welchen Typ Sie erwarten, dass eine Funktion zurückgegeben wird, und kann entsprechend an eine andere Implementierung senden. Dies bedeutet, dass dieselbe Funktion, die für dieselben Argumente aufgerufen wird, etwas völlig anderes tun kann, je nachdem, was mit dem Rückgabewert getan wird. Zum Beispiel:
%Vor%Gleichermaßen können Sie sogar polymorphe Konstanten haben, die für jede Klasseninstanz einen anderen Wert haben:
%Vor%Sie können dies nicht wirklich in einer dynamischen Sprache tun, da es keine Typrückschlüsse gibt. Sie können es ein wenig fälschen, indem Sie Objekte umleiten, die "die Art von Ergebnissen darstellen, die ich möchte", und diese als Ihren Dispatcher verwenden, aber es ist nicht wirklich dasselbe.
Mehrfache Versendung ( Beispiel in Julia Sprache ) hat ähnliche Zwecke mit Typklassen. Multiple Dispatch bietet Kompilierzeit-Polymorphismus (genau wie Typklassen), während Objektschnittstellen in typischen dynamischen Sprachen (d. H. Python) normalerweise auf Laufzeitpolymorphismus beschränkt sind. Multiple Dispatch bietet eine bessere Performance als übliche Interfaces, die Sie in dynamischen objektorientierten Sprachen sehen können, daher ist es in dynamischen Sprachen sehr sinnvoll.
Es gibt einige Implementierungen für mehrere Dispatchs für Python, aber ich bin mir nicht sicher, ob sie kompilieren. Zeit Polymorphismus.
Multimethods scheinen in Clojure den Trick zu machen. Definieren wir zum Beispiel eine Funktion plus
, die Zahlen hinzufügt, aber die Zeichenfolgendarstellung von irgendetwas anderem verkettet.
Multimethods sind Funktionen ( (ifn? plus)
is true
), also so erstklassig, wie Sie es sich wünschen:
Sie können in Python (im selben Bereich) nicht mehrere Funktionen mit demselben Namen definieren. Wenn Sie dies tun, überschreibt die zweite Definition die erste und ist die einzige aufgerufene (zumindest wenn beide im selben Bereich sind - offensichtlich können Sie Klassenmethoden in verschiedenen Klassen haben, die einen Namen teilen). Die Parameterliste ist auch typenunwissend. Selbst wenn Sie Funktionen zweimal definieren könnten, würde der Interpreter sie nur anhand der Anzahl der Parameter und nicht anhand des Typs unterscheiden. Was Sie tun müssen, ist eine einzelne Funktion schreiben, die mehrere verschiedene Argumentlisten behandeln kann, und dann überprüfen Sie ihren Typ innerhalb dieser Funktion.
Die einfachste Methode besteht darin, Standardparameter und Schlüsselwortargumente zu verwenden.
Angenommen, Sie hatten eine Funktion wie folgt:
%Vor%Sie können diese Funktion mit Positionsargumenten wie folgt aufrufen:
%Vor%Das wird alles funktionieren, aber Sie wollen vielleicht nicht immer jeden Standard ändern. Was, wenn du einen Standard-Apfelkuchen backen willst, nur in einem anderen Universum? Nun, Sie können es mit Schlüsselwortargumenten aufrufen:
%Vor%In diesem Fall werden die Standardargumente für filling und oxtemp verwendet, und nur das Argument für das Universum (und die Kruste, die immer gegeben werden muss, da es keinen Standardwert gibt) werden geändert. Die einzige Regel hier ist, dass alle Schlüsselwortargumente beim Aufruf der Funktion rechts von irgendwelchen Positionsargumenten stehen müssen. Die Reihenfolge der Schlüsselwortargumente spielt keine Rolle, z.B. das wird auch funktionieren:
%Vor%Aber das wird nicht:
%Vor%Nun, was ist, wenn das Verhalten Ihrer Funktion völlig unterschiedlich ist, abhängig davon, welche Argumente übergeben werden? Zum Beispiel haben Sie eine Multiplikationsfunktion, die entweder zwei Ganzzahlen oder eine Ganzzahl und eine Liste von ganzen Zahlen oder zwei Listen von Ganzzahlen verwendet und diese multipliziert. In dieser Situation möchten Sie als Aufrufer nur Schlüsselwortargumente verwenden. Sie können die Funktionsdefinition wie folgt einrichten:
%Vor%(Oder: Verwenden Sie None anstelle von False.)
Wenn Sie zwei ganze Zahlen multiplizieren müssen, rufen Sie wie folgt auf:
%Vor%Hinweis: Sie können das obige auch mit nur zwei Positionsargumenten ausführen und ihren Typ manuell in der Funktion überprüfen, entweder mithilfe der Funktion type () oder mithilfe von try: außer: blocks und z. Methoden aufrufen, die nur auf Listen oder Ganzzahlen für jedes Argument funktionieren.
Es gibt viele andere Möglichkeiten, wie Sie in Python vorgehen könnten. Ich habe gerade versucht, das Einfachste zu beschreiben. Wenn Sie mehr erfahren möchten, empfehle ich den offiziellen Python-Tutorial-Abschnitt zu Funktionen definieren und der nächste Abschnitt, Mehr über Definieren von Funktionen (hier geht es ausführlicher um Positions- und Schlüsselwortargumente, sowie um die Syntax von * args und ** kwargs, mit denen Sie Funktionen mit Parameterlisten variabler Länge definieren können, ohne Standard verwenden zu müssen) Werte).
Bis zu einem gewissen Grad wird diese Funktionalität durch Klassenmethoden repliziert. Beispielsweise entspricht die __repr__
-Methode ungefähr der% type-Klasse in Haskell:
oder in Python
%Vor% Offensichtlich wird je nach Typ (im Falle von Haskell) oder Klasse (im Fall von Python) eine andere Funktion aufgerufen, auf die Show
/ show
angewendet wird / angerufen.
Eine engere Annäherung an Sprachen, die sie unterstützen, sind Interfaces - abstrakte Klassen mit allen ihren Methoden, die auch abstrakt sind (sie heißen Interfaces in Java und virtuelle Klassen in C ++). Sie neigen dazu, sie in dynamisch typisierten Sprachen nicht so häufig zu sehen, da der Hauptpunkt einer Schnittstelle darin besteht, eine Reihe von Methoden und ihre zugeordneten Typen zu deklarieren, denen eine implementierende Klasse entsprechen muss. Ohne statische Typen gibt es nicht viel zu sagen, was die Typen sein sollten.