paint-brush
Traits Mojo : comment se comparent-ils aux interfaces Go ?par@a2svior
1,706 lectures
1,706 lectures

Traits Mojo : comment se comparent-ils aux interfaces Go ?

par Valentin Erokhin6m2023/12/26
Read on Terminal Reader

Trop long; Pour lire

Une procédure pas à pas sur l'utilisation des Mojo Traits avec des exemples et une comparaison des Mojo Traits to Go Interfaces. Limitations et mises en garde des traits Mojo.
featured image - Traits Mojo : comment se comparent-ils aux interfaces Go ?
Valentin Erokhin HackerNoon profile picture
0-item

Des traits ont été introduits récemment dans Mojo, alors j'ai pensé les essayer. Actuellement, les caractéristiques intégrées incluent CollectionElement , Copyable , Destructable , Intable , Movable , Sized et Stringable (on dirait que le suffixe "-able" fait partie de ces conventions de dénomination !).

Les traits fonctionnent sur les structures. Pour implémenter un trait, vous ajoutez simplement une méthode à une structure qui correspond au trait ; puis passez le nom du trait en paramètre :

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


Le décorateur @value de Mojo insère les méthodes de cycle de vie comme __init__() , __copyinit__() et __moveinit__() dans la structure, simplifiant un peu notre vie car nous n'avons pas besoin de les ajouter nous-mêmes.


Les traits dans Mojo ne prennent pas encore en charge les implémentations par défaut des méthodes, d'où le ... dans le corps de la méthode Quackable ci-dessus. Vous pouvez également utiliser pass , qui aura le même effet qu’en Python.

Traits Mojo et interfaces Go

Malgré le nom différent, l'approche de base des traits dans Mojo me rappelle les interfaces Go. Dans Go, vous définiriez une structure Duck et implémenterais une interface Quackable comme celle-ci :


 type Quackable interface { quack() }


Et pour créer une structure qui satisfait cette interface :


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


Comparez-le à une implémentation Mojo :


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


Je pense que la version Mojo est encore plus concise, imbriquant la définition de la méthode quack() dans le type de structure Duck . En plus de travailler sur les structures, les traits dans Mojo ne nécessitent pas de mot-clé implements , tout comme dans Go.


Il est intéressant de noter que la documentation Mojo indique que les implémentations par défaut ne sont pas encore autorisées, ce qui signifie qu'elles pourraient l'être à l'avenir. Cela signifie que Mojo pourrait poursuivre une approche différente de Go, qui se concentre sur les structures satisfaisant une interface et non sur sa mise en œuvre .


Les implémentations de méthodes par défaut ne sont pas nécessaires dans Go, et un effet similaire est obtenu avec l'intégration, car l'implémentation d'interface/trait est simplement un concept qui n'existe pas dans Go. Les choses pourraient être différentes à Mojo.

Comment utiliser les traits Mojo

La valeur des traits et des interfaces est de rendre le code réutilisable. Par exemple, dans Mojo, vous pouvez écrire des fonctions qui acceptent des types de traits…


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


Et puis transmettez différentes structures qui implémentent le même trait en entrée - tout fonctionnera ! Par exemple, voici une structure Goose qui implémente Quackable :


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


Et ici, pour appeler make_it_quack à la fois sur Goose et Duck (rappelez-vous, vous avez besoin de la fonction main de Mojo comme point d'entrée à votre programme) :


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


Le résultat de ceci sera


 Quack! Honk!


Erreurs de traits

Si j'essaie de transmettre quelque chose qui n'implémente pas le trait Quackable à la fonction make_it_quack , disons StealthCow , le programme ne compilera pas :


 @value struct StealthCow(): pass


 make_it_quack(StealthCow())


Le message d'erreur ci-dessous aurait pu être plus descriptif ; peut-être que l'équipe Mojo l'améliorera ?


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


Idem si je supprime la méthode quack de Goose ; ici, nous obtenons une belle erreur descriptive :


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


L'avantage de ce type de vérification de type statique par rapport à l'approche Python pure est que nous pouvons détecter facilement les erreurs et éviter de les transmettre à la production, car le code ne sera tout simplement pas compilé.

Héritage

Les traits dans Mojo prennent déjà en charge l'héritage, donc notre trait « Quackable » peut étendre un trait « Audible » comme ceci :


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


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


Cela signifie qu'une structure Duck devra implémenter à la fois quack et make_sound pour se conformer au trait Quackable .


Ceci est similaire au concept « d'intégration d'interface » dans Go, où pour créer une nouvelle interface qui hérite d'autres interfaces, vous intégreriez des interfaces parents comme ceci :


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

Méthodes statiques

Les traits acceptent également les méthodes statiques qui fonctionnent sans créer d'instance de structure :


 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()


Vous appelez une méthode statique comme ceci :


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


Ce qui produira :


 Quack! Honk! Swimming


Notez que lors du dernier appel de méthode, aucune instance de Duck n'est créée. C'est ainsi que fonctionnent les méthodes statiques en Python, s'éloignant quelque peu de la programmation orientée objet. Mojo s'appuie sur cette fonctionnalité Python.

Limites des traits Mojo par rapport aux interfaces Go

Fait intéressant, l'astuce Go avec une interface{} qui permet de transmettre n'importe quel type et qui était populaire auprès de la communauté Go avant l'introduction de Go Generics, ne fonctionnera pas avec une fonction fn typée Mojo.


Votre structure doit implémenter au moins une des méthodes de cycle de vie telles que __len__ ou __str__ , ce qui dans ce cas la rendrait conforme à Sized ou Stringable , à utiliser avec des fonctions qui acceptent les types de traits.


Ce n'est en fait pas une réelle limitation et cela a beaucoup de sens dans Mojo, puisque, avec Mojo, si vous avez besoin d'un typage dynamique, vous pouvez simplement vous rabattre sur la fonction def plus flexible, puis appliquer la magie Python habituelle pour travailler avec un inconnu. les types.


Les fonctions Mojo fn plus strictes fonctionnent également sur des structures génériques utilisant des types comme DynamicVector ; en savoir plus à ce sujet ici .


Une autre limitation que je vois concerne les structures et leurs méthodes plutôt que les traits et constitue un obstacle potentiel à la mise en œuvre d'une architecture propre/à la séparation du code en différentes parties.


Considérons l'un des exemples précédents avec une définition de méthode struct Go vs. Mojo :


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


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


L'exemple Mojo, suivant une syntaxe plus proche de celle de Python, imbrique la définition de la méthode directement dans la structure, tandis que la version Go lui permet de la séparer de la structure elle-même. Dans cette version, si j'ai une structure très longue avec de nombreux types et méthodes, elle sera un peu plus difficile à lire.


Ce n’est cependant pas une différence critique, juste quelque chose dont il faut être conscient.


Les traits Mojo sont déjà très utiles, même si le langage en est à ses débuts. Bien que la philosophie Go soit axée sur la simplicité et tente de réduire les fonctionnalités au minimum, il est probable que nous verrons davantage de fonctionnalités ajoutées aux traits Mojo à l'avenir, les rendant encore plus puissants et permettant des tonnes de cas d'utilisation différents.