Los rasgos se introdujeron en Mojo recientemente, así que pensé en probarlos. Actualmente, los rasgos integrados incluyen , , , , , y (¡parece que el sufijo "-able" existe en estas convenciones de nomenclatura!). Los rasgos funcionan en estructuras. Para implementar un rasgo, simplemente agrega un método a una estructura que coincida con el rasgo; luego pase el nombre del rasgo como parámetro: CollectionElement Copyable Destructable Intable Movable Sized Stringable @value struct Duck(Quackable): fn quack(self): print("Quack!") El decorador en Mojo inserta métodos de ciclo de vida como , y en la estructura, simplificando un poco nuestra vida ya que no tenemos que agregarlos nosotros mismos. @value __init__() __copyinit__() __moveinit__() Los rasgos en Mojo aún no admiten implementaciones predeterminadas para métodos, por lo tanto, en el cuerpo del método anterior. También puedes usar , que tendrá el mismo efecto que en Python. ... Quackable pass Rasgos de Mojo versus interfaces Go A pesar del nombre diferente, el enfoque básico de los rasgos en Mojo me recuerda a las interfaces de Go. En Go, definirías una estructura e implementarías una interfaz como esta: Duck Quackable type Quackable interface { quack() } Y para hacer una estructura que satisfaga esta interfaz: type Duck struct {} func (d Duck) quack() { fmt.Println("Quack!") } Compárelo con una implementación de Mojo: trait Quackable: fn quack(self): ... @value struct Duck(Quackable): fn quack(self): print("Quack!") Creo que la versión Mojo es aún más concisa, ya que anida la definición del método dentro del tipo de estructura . Además de trabajar en estructuras, los rasgos en Mojo no requieren una palabra clave , como en Go. quack() Duck implements Curiosamente, en los documentos de Mojo se señala que las implementaciones predeterminadas no están permitidas, lo que significa que podrían permitirse en el futuro. Esto significa que Mojo podría seguir un enfoque diferente al de Go, que se centra en estructuras una interfaz, no . aún que satisfacen en implementarla Las implementaciones de métodos predeterminados no son necesarias en Go, y se logra un efecto similar con la incrustación, ya que la implementación de interfaz/rasgo es simplemente un concepto que no existe en Go. Las cosas podrían ser diferentes en Mojo. Cómo utilizar los rasgos de Mojo El valor de los rasgos y las interfaces es hacer que el código sea reutilizable. Por ejemplo, en Mojo, puedes escribir funciones que acepten tipos de rasgos... fn make_it_quack[T: Quackable](could_be_duck: T): could_be_duck.quack() Y luego pase diferentes estructuras que implementen el mismo rasgo como entrada: ¡todo funcionará! Por ejemplo, aquí hay una estructura que implementa : Goose Quackable @value struct Goose(Quackable): fn quack(self): print("Honk!") Y aquí, para llamar tanto en como en (recuerda, necesitas la función en Mojo como punto de entrada a tu programa): make_it_quack Goose Duck main def main(): make_it_quack(Duck()) make_it_quack(Goose()) El resultado de esto será Quack! Honk! Errores de rasgos Si intenté pasar algo que no implementa el rasgo a la función , digamos , el programa no se compilará: Quackable make_it_quack StealthCow @value struct StealthCow(): pass make_it_quack(StealthCow()) El siguiente mensaje de error podría haber sido más descriptivo; ¿Quizás el equipo de Mojo lo mejore? error: invalid call to 'make_it_quack': callee expects 1 input parameter, but 0 were specified Lo mismo si elimino el método de ; Aquí obtenemos un bonito error descriptivo: quack Goose struct 'Goose' does not implement all requirements for 'Quackable' required function 'quack' is not implemented La ventaja de este tipo de verificación de tipos estáticos sobre el enfoque puro de Python es que podemos detectar errores fácilmente y evitar enviarlos a producción, ya que el código simplemente no se compilará. Herencia Los rasgos en Mojo ya admiten herencia, por lo que nuestro rasgo "Quackable" puede extender un rasgo "Audible" de esta manera: trait Audible: fn make_sound(self): ... trait Quackable(Audible): fn quack(self): ... Esto significa que una estructura tendrá que implementar tanto como para ajustarse al rasgo . Duck quack make_sound Quackable Esto es similar al concepto de "incrustación de interfaz" en Go, donde para crear una nueva interfaz que herede de otras interfaces, debería incrustar interfaces principales como esta: type Quackable interface { Audible // includes methods of Audible in Quackable's method set } Métodos estáticos Los rasgos también aceptan métodos estáticos que funcionan sin crear una instancia de una estructura: 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() Llamas a un método estático como este: def main(): make_it_quack(Duck()) make_it_quack(Goose()) Duck.swim() Que generará: Quack! Honk! Swimming Observe que en la última llamada al método, no se crea una instancia de Duck. Así funcionan los métodos estáticos en Python, alejándose un poco de la programación orientada a objetos. Mojo se basa en esta funcionalidad de Python. Limitaciones de los rasgos de Mojo en comparación con las interfaces Go Curiosamente, el truco de Go con una que permite pasar cualquier tipo y que era popular entre la comunidad de Go antes de que se introdujeran Go Generics, no funcionará con una función escrita en Mojo. interface{} fn Su estructura debe implementar al menos uno de los métodos del ciclo de vida como o , que en este caso haría que se ajuste a o , para usarse con funciones que acepten tipos de rasgos. __len__ __str__ Sized Stringable En realidad, esto no es una limitación real y tiene mucho sentido en Mojo, ya que, con Mojo, si necesita escritura dinámica, puede recurrir a la función más flexible y luego aplicar la magia habitual de Python para trabajar con desconocidos. tipos. def Las funciones más estrictas de Mojo también funcionan en estructuras genéricas usando tipos como ; Lea mas sobre eso, . fn DynamicVector aqui Otra limitación que veo tiene que ver con las estructuras y sus métodos en lugar de con los rasgos y es un obstáculo potencial para implementar una arquitectura limpia/separar el código en diferentes partes. Considere uno de los ejemplos anteriores con una definición del método de estructura Go vs. Mojo: type Duck struct {} func (d Duck) quack() { fmt.Println("Quack!") } @value struct Duck(Quackable): fn quack(self): print("Quack!") El ejemplo de Mojo, siguiendo una sintaxis más parecida a Python, anida la definición del método directamente dentro de la estructura, mientras que la versión Go le permite separarla de la propia estructura. En esta versión, si tengo una estructura muy larga con muchos tipos y métodos, será algo más difícil de leer. Sin embargo, no es una diferencia crítica, sólo algo a tener en cuenta. Los rasgos de Mojo ya son bastante útiles, a pesar de que el lenguaje se encuentra en sus inicios. Si bien la filosofía de Go tiene que ver con la simplicidad y trata de mantener las funciones al mínimo, es probable que veamos más funciones agregadas a los rasgos de Mojo en el futuro, haciéndolos aún más poderosos y permitiendo toneladas de casos de uso diferentes.