paint-brush
Mojo-Eigenschaften: Wie vergleichen sie sich mit Go-Schnittstellen?von@a2svior
1,846 Lesungen
1,846 Lesungen

Mojo-Eigenschaften: Wie vergleichen sie sich mit Go-Schnittstellen?

von Valentin Erokhin6m2023/12/26
Read on Terminal Reader

Zu lang; Lesen

Eine exemplarische Vorgehensweise für die Arbeit mit Mojo Traits anhand von Beispielen und einem Vergleich von Mojo Traits mit Go-Schnittstellen. Einschränkungen und Vorbehalte der Mojo-Eigenschaften.
featured image - Mojo-Eigenschaften: Wie vergleichen sie sich mit Go-Schnittstellen?
Valentin Erokhin HackerNoon profile picture
0-item

Eigenschaften wurden kürzlich in Mojo eingeführt, also dachte ich, ich probiere sie mal aus. Derzeit umfassen die integrierten Merkmale CollectionElement , Copyable , Destructable , Intable , Movable , Sized und Stringable (das Suffix „-able“ scheint in diesen Namenskonventionen enthalten zu sein!).

Merkmale funktionieren auf Strukturen. Um ein Merkmal zu implementieren, fügen Sie einfach eine Methode zu einer Struktur hinzu, die dem Merkmal entspricht. Übergeben Sie dann den Merkmalsnamen als Parameter:

 @value struct Duck(Quackable): fn quack(self): print("Quack!")


Der @value Dekorator in Mojo fügt die Lebenszyklusmethoden wie __init__() , __copyinit__() und __moveinit__() in die Struktur ein, was unser Leben ein wenig vereinfacht, da wir sie nicht selbst hinzufügen müssen.


Merkmale in Mojo unterstützen noch keine Standardimplementierungen für Methoden, daher das ... im Hauptteil der Quackable -Methode oben. Sie können auch pass verwenden, was den gleichen Effekt wie in Python hat.

Mojo-Eigenschaften vs. Go-Schnittstellen

Trotz des anderen Namens erinnert mich der grundlegende Ansatz für Merkmale in Mojo an Go-Schnittstellen. In Go würden Sie eine Duck Struktur definieren und eine Quackable Schnittstelle wie folgt implementieren:


 type Quackable interface { quack() }


Und um eine Struktur zu erstellen, die diese Schnittstelle erfüllt:


 type Duck struct {} func (d Duck) quack() { fmt.Println("Quack!") }


Vergleichen Sie es mit einer Mojo-Implementierung:


 trait Quackable: fn quack(self): ... @value struct Duck(Quackable): fn quack(self): print("Quack!")


Ich denke, die Mojo-Version ist noch prägnanter, da sie die Methodendefinition quack() innerhalb des Strukturtyps Duck verschachtelt. Zusätzlich zur Arbeit an Strukturen erfordern Merkmale in Mojo kein Schlüsselwort implements , genau wie in Go.


Interessanterweise wird in den Mojo-Dokumenten darauf hingewiesen, dass Standardimplementierungen noch nicht zulässig sind, was bedeutet, dass sie möglicherweise in Zukunft zulässig sind. Das bedeutet, dass Mojo möglicherweise einen anderen Ansatz verfolgt als Go, der sich auf Strukturen konzentriert, die eine Schnittstelle erfüllen , und nicht auf deren Implementierung .


Standardmethodenimplementierungen sind in Go nicht erforderlich, und ein ähnlicher Effekt wird mit der Einbettung erzielt, da die Schnittstellen-/Merkmalsimplementierung lediglich ein Konzept ist, das in Go nicht existiert. In Mojo könnte es anders sein.

So verwenden Sie Mojo-Eigenschaften

Der Wert von Merkmalen und Schnittstellen besteht darin, Code wiederverwendbar zu machen. In Mojo können Sie beispielsweise Funktionen schreiben, die Merkmalstypen akzeptieren ...


 fn make_it_quack[T: Quackable](could_be_duck: T): could_be_duck.quack()


Und dann übergeben Sie verschiedene Strukturen, die das gleiche Merkmal als Eingabe implementieren – alles wird einfach funktionieren! Hier ist zum Beispiel eine Goose Struktur, die Quackable implementiert:


 @value struct Goose(Quackable): fn quack(self): print("Honk!")


Und hier, um make_it_quack sowohl auf der Goose als auch auf der Duck aufzurufen (denken Sie daran, dass Sie die main in Mojo als Einstiegspunkt in Ihr Programm benötigen):


 def main(): make_it_quack(Duck()) make_it_quack(Goose())


Die Ausgabe davon wird sein


 Quack! Honk!


Merkmalsfehler

Wenn ich versuche, etwas an die Funktion make_it_quack zu übergeben, das das Quackable Merkmal nicht implementiert, sagen wir StealthCow , lässt sich das Programm nicht kompilieren:


 @value struct StealthCow(): pass


 make_it_quack(StealthCow())


Die folgende Fehlermeldung hätte aussagekräftiger sein können; Vielleicht verbessert das Mojo-Team es?


 error: invalid call to 'make_it_quack': callee expects 1 input parameter, but 0 were specified


Das Gleiche gilt, wenn ich die quack Methode aus Goose entferne. Hier erhalten wir einen schönen, beschreibenden Fehler:


 struct 'Goose' does not implement all requirements for 'Quackable' required function 'quack' is not implemented


Der Vorteil dieser Art der statischen Typprüfung gegenüber dem reinen Python-Ansatz besteht darin, dass wir Fehler leicht erkennen können und vermeiden, dass Fehler in die Produktion gelangen, da der Code einfach nicht kompiliert werden kann.

Nachlass

Merkmale in Mojo unterstützen bereits die Vererbung, daher kann unser Merkmal „Quackable“ ein Merkmal „Audible“ wie folgt erweitern:


 trait Audible: fn make_sound(self): ...


 trait Quackable(Audible): fn quack(self): ...


Das bedeutet, dass eine Duck Struktur sowohl quack als auch make_sound implementieren muss, um dem Quackable Merkmal zu entsprechen.


Dies ähnelt dem Konzept der „Schnittstelleneinbettung“ in Go. Um eine neue Schnittstelle zu erstellen, die von anderen Schnittstellen erbt, würden Sie übergeordnete Schnittstellen wie folgt einbetten:


 type Quackable interface { Audible // includes methods of Audible in Quackable's method set }

Statische Methoden

Traits akzeptieren auch statische Methoden, die funktionieren, ohne eine Instanz einer Struktur zu erstellen:


 trait Swims: @staticmethod fn swim(): ... @value struct Duck(Quackable, Swims): fn quack(self): print("Quack!") @staticmethod fn swim(): print("Swimming")


 fn make_it_swim[T: Swims](): T.swim()


Sie rufen eine statische Methode wie folgt auf:


 def main(): make_it_quack(Duck()) make_it_quack(Goose()) Duck.swim()


Was wird ausgeben:


 Quack! Honk! Swimming


Beachten Sie, dass beim letzten Methodenaufruf keine Duck-Instanz erstellt wird. So funktionieren statische Methoden in Python und weichen etwas von der objektorientierten Programmierung ab. Mojo baut auf dieser Python-Funktionalität auf.

Einschränkungen von Mojo-Merkmalen im Vergleich zu Go-Schnittstellen

Interessanterweise funktioniert der Go-Trick mit einem leeren interface{} , der die Übergabe eines beliebigen Typs ermöglicht und vor der Einführung von Go Generics in der Go-Community beliebt war, nicht mit einer Mojo-typisierten Funktion fn .


Ihre Struktur muss mindestens eine der Lebenszyklusmethoden wie __len__ oder __str__ implementieren, wodurch sie in diesem Fall Sized oder Stringable entspricht und mit Funktionen verwendet werden kann, die Merkmalstypen akzeptieren.


Dies ist eigentlich keine wirkliche Einschränkung und macht in Mojo sehr viel Sinn, da Sie bei Mojo, wenn Sie dynamisches Tippen benötigen, einfach auf die flexiblere def Funktion zurückgreifen und dann die übliche Python-Magie anwenden können, um mit Unbekanntem zu arbeiten Typen.


Die strengeren Mojo- fn Funktionen funktionieren auch mit generischen Strukturen unter Verwendung von Typen wie DynamicVector ; Lesen Sie hier mehr darüber.


Eine weitere Einschränkung, die ich sehe, hat eher mit Strukturen und ihren Methoden als mit Merkmalen zu tun und stellt ein potenzielles Hindernis für die Implementierung von Clean Architecture bzw. die Aufteilung von Code in verschiedene Teile dar.


Betrachten Sie eines der vorherigen Beispiele mit einer Go vs. Mojo-Strukturmethodendefinition:


 type Duck struct {} func (d Duck) quack() { fmt.Println("Quack!") }


 @value struct Duck(Quackable): fn quack(self): print("Quack!")


Das Mojo-Beispiel folgt einer eher Python-ähnlichen Syntax und verschachtelt die Methodendefinition direkt in der Struktur, während die Go-Version es ermöglicht, sie von der Struktur selbst zu trennen. Wenn ich in dieser Version eine sehr lange Struktur mit vielen Typen und Methoden habe, ist sie etwas schwieriger zu lesen.


Es handelt sich jedoch nicht um einen entscheidenden Unterschied, sondern lediglich um etwas, dessen man sich bewusst sein sollte.


Mojo-Eigenschaften sind bereits ziemlich nützlich, obwohl die Sprache noch in den Kinderschuhen steckt. Während es bei der Go-Philosophie vor allem um Einfachheit geht und versucht, die Funktionen auf ein Minimum zu beschränken, ist es wahrscheinlich, dass den Mojo-Eigenschaften in Zukunft weitere Funktionen hinzugefügt werden, wodurch sie noch leistungsfähiger werden und unzählige verschiedene Anwendungsfälle möglich werden.