As características foram introduzidas no Mojo recentemente, então pensei em experimentá-las. Atualmente, as características integradas incluem , , , , , e (parece que o sufixo "-able" é uma coisa nessas convenções de nomenclatura!). As características funcionam em estruturas. Para implementar uma característica, basta adicionar um método a uma estrutura que corresponda à característica; em seguida, passe o nome da característica como parâmetro: CollectionElement Copyable Destructable Intable Movable Sized Stringable @value struct Duck(Quackable): fn quack(self): print("Quack!") O decorador no Mojo insere os métodos de ciclo de vida como , e na estrutura, simplificando um pouco nossa vida, pois não precisamos adicioná-los nós mesmos. @value __init__() __copyinit__() __moveinit__() As características no Mojo ainda não suportam implementações padrão para métodos, portanto, o no corpo do método acima. Você também pode usar , que terá o mesmo efeito que em Python. ... Quackable pass Características Mojo vs. Interfaces Go Apesar do nome diferente, a abordagem básica das características no Mojo me lembra as interfaces Go. No Go, você definiria uma estrutura e implementaria uma interface como esta: Duck Quackable type Quackable interface { quack() } E para fazer uma estrutura que satisfaça esta interface: type Duck struct {} func (d Duck) quack() { fmt.Println("Quack!") } Compare-o com uma implementação do Mojo: trait Quackable: fn quack(self): ... @value struct Duck(Quackable): fn quack(self): print("Quack!") Acho que a versão Mojo é ainda mais concisa, aninhando a definição do método dentro do tipo de estrutura . Além de trabalhar em estruturas, as características no Mojo não requerem uma palavra-chave , assim como no Go. quack() Duck implements Curiosamente, nos documentos do Mojo, afirma-se que as implementações padrão não são permitidas, o que significa que poderão ser permitidas no futuro. Isso significa que o Mojo pode seguir uma abordagem diferente do Go, que se concentra em estruturas uma interface, e não em -la. ainda que satisfazem implementá Implementações de métodos padrão não são necessárias em Go, e um efeito semelhante é alcançado com a incorporação, já que a implementação de interface/característica é simplesmente um conceito que não existe em Go. As coisas podem ser diferentes no Mojo. Como usar características Mojo O valor das características e interfaces é tornar o código reutilizável. Por exemplo, no Mojo, você pode escrever funções que aceitam tipos de características… fn make_it_quack[T: Quackable](could_be_duck: T): could_be_duck.quack() E então passe estruturas diferentes que implementam a mesma característica da entrada - tudo funcionará! Por exemplo, aqui está uma estrutura que implementa : Goose Quackable @value struct Goose(Quackable): fn quack(self): print("Honk!") E aqui, para chamar tanto no quanto no (lembre-se, você precisa da função no Mojo como ponto de entrada para o seu programa): make_it_quack Goose Duck main def main(): make_it_quack(Duck()) make_it_quack(Goose()) A saída disso será Quack! Honk! Erros de característica Se eu tentei passar algo que não implementa o traço para a função , digamos , o programa não será compilado: Quackable make_it_quack StealthCow @value struct StealthCow(): pass make_it_quack(StealthCow()) A mensagem de erro abaixo poderia ter sido mais descritiva; talvez a equipe Mojo melhore isso? error: invalid call to 'make_it_quack': callee expects 1 input parameter, but 0 were specified O mesmo se eu remover o método de ; aqui, obtemos um erro descritivo agradável: quack Goose struct 'Goose' does not implement all requirements for 'Quackable' required function 'quack' is not implemented A vantagem desse tipo de verificação de tipo estático sobre a abordagem Python pura é que podemos detectar erros facilmente e evitar enviá-los para a produção, pois o código simplesmente não será compilado. Herança As características no Mojo já suportam herança, então nossa característica `Quackable` pode estender uma característica `Audible` da seguinte forma: trait Audible: fn make_sound(self): ... trait Quackable(Audible): fn quack(self): ... Isso significa que uma estrutura terá que implementar tanto quanto para estar em conformidade com a característica . Duck quack make_sound Quackable Isso é semelhante ao conceito de "incorporação de interface" em Go, onde para criar uma nova interface que herda de outras interfaces, você incorporaria interfaces pai assim: type Quackable interface { Audible // includes methods of Audible in Quackable's method set } Métodos estáticos As características também aceitam métodos estáticos que funcionam sem criar uma instância de uma estrutura: 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() Você chama um método estático como este: def main(): make_it_quack(Duck()) make_it_quack(Goose()) Duck.swim() O que produzirá: Quack! Honk! Swimming Observe que na última chamada de método, uma instância Duck não é criada. É assim que os métodos estáticos em Python funcionam, afastando-se um pouco da programação orientada a objetos. Mojo baseia-se nesta funcionalidade Python. Limitações das características do Mojo em comparação com interfaces Go Curiosamente, o truque do Go com uma que permite transmitir qualquer tipo e era popular entre a comunidade Go antes da introdução do Go Generics não funcionará com uma função do tipo Mojo. interface{} fn Sua estrutura deve implementar pelo menos um dos métodos de ciclo de vida como ou , que neste caso, o tornaria compatível com ou , para ser usado com funções que aceitam tipos de características. __len__ __str__ Sized Stringable Na verdade, esta não é uma limitação real e faz muito sentido no Mojo, já que, com o Mojo, se você precisar de digitação dinâmica, você pode simplesmente recorrer à função mais flexível e, em seguida, aplicar a magia usual do Python para trabalhar com desconhecidos. tipos. def As funções mais estritas do Mojo também funcionam em estruturas genéricas usando tipos como ; leia mais sobre isso . fn DynamicVector aqui Outra limitação que vejo tem a ver com estruturas e seus métodos, e não com características, e é um obstáculo potencial para a implementação da Arquitetura Limpa/separação do código em partes diferentes. Considere um dos exemplos anteriores com uma definição de método de estrutura Go vs. Mojo: type Duck struct {} func (d Duck) quack() { fmt.Println("Quack!") } @value struct Duck(Quackable): fn quack(self): print("Quack!") O exemplo Mojo, seguindo uma sintaxe mais parecida com Python, aninha a definição do método diretamente dentro da estrutura, enquanto a versão Go permite separá-la da própria estrutura. Nesta versão, se eu tiver uma estrutura muito longa com muitos tipos e métodos, será um pouco mais difícil de ler. Porém, não é uma diferença crítica, apenas algo para estar ciente. As características do Mojo já são bastante úteis, apesar da linguagem estar em seus primórdios. Embora a filosofia Go seja baseada na simplicidade e tente manter os recursos no mínimo, é provável que vejamos mais funcionalidades adicionadas às características do Mojo no futuro, tornando-as ainda mais poderosas e permitindo vários casos de uso diferentes.