Des traits ont été introduits récemment dans Mojo, alors j'ai pensé les essayer. Actuellement, les caractéristiques intégrées incluent , , , , , et (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 : CollectionElement Copyable Destructable Intable Movable Sized Stringable @value struct Duck(Quackable): fn quack(self): print("Quack!") Le décorateur de Mojo insère les méthodes de cycle de vie comme , et dans la structure, simplifiant un peu notre vie car nous n'avons pas besoin de les ajouter nous-mêmes. @value __init__() __copyinit__() __moveinit__() 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 ci-dessus. Vous pouvez également utiliser , qui aura le même effet qu’en Python. ... Quackable pass 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 et implémenterais une interface comme celle-ci : Duck Quackable 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 dans le type de structure . En plus de travailler sur les structures, les traits dans Mojo ne nécessitent pas de mot-clé , tout comme dans Go. quack() Duck implements Il est intéressant de noter que la documentation Mojo indique que les implémentations par défaut ne sont pas 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 une interface et non sur sa . encore satisfaisant 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 qui implémente : Goose Quackable @value struct Goose(Quackable): fn quack(self): print("Honk!") Et ici, pour appeler à la fois sur et (rappelez-vous, vous avez besoin de la fonction de Mojo comme point d'entrée à votre programme) : make_it_quack Goose Duck main 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 à la fonction , disons , le programme ne compilera pas : Quackable make_it_quack StealthCow @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 de ; ici, nous obtenons une belle erreur descriptive : quack Goose 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 devra implémenter à la fois et pour se conformer au trait . Duck quack make_sound 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 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 typée Mojo. interface{} fn Votre structure doit implémenter au moins une des méthodes de cycle de vie telles que ou , ce qui dans ce cas la rendrait conforme à ou , à utiliser avec des fonctions qui acceptent les types de traits. __len__ __str__ Sized Stringable 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 plus flexible, puis appliquer la magie Python habituelle pour travailler avec un inconnu. les types. def Les fonctions Mojo plus strictes fonctionnent également sur des structures génériques utilisant des types comme ; en savoir plus à ce sujet . fn DynamicVector 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.