paint-brush
Traços do Mojo: como eles se comparam às interfaces Go?por@a2svior
1,836 leituras
1,836 leituras

Traços do Mojo: como eles se comparam às interfaces Go?

por Valentin Erokhin6m2023/12/26
Read on Terminal Reader

Muito longo; Para ler

Um passo a passo sobre como trabalhar com Mojo Traits com exemplos e uma comparação entre Mojo Traits e Go Interfaces. Limitações e advertências das características do Mojo.
featured image - Traços do Mojo: como eles se comparam às interfaces Go?
Valentin Erokhin HackerNoon profile picture
0-item

As características foram introduzidas no Mojo recentemente, então pensei em experimentá-las. Atualmente, as características integradas incluem CollectionElement , Copyable , Destructable , Intable , Movable , Sized e Stringable (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:

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


O decorador @value no Mojo insere os métodos de ciclo de vida como __init__() , __copyinit__() e __moveinit__() na estrutura, simplificando um pouco nossa vida, pois não precisamos adicioná-los nós mesmos.


As características no Mojo ainda não suportam implementações padrão para métodos, portanto, o ... no corpo do método Quackable acima. Você também pode usar pass , que terá o mesmo efeito que em Python.

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 Duck e implementaria uma interface Quackable como esta:


 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 quack() dentro do tipo de estrutura Duck . Além de trabalhar em estruturas, as características no Mojo não requerem uma palavra-chave implements , assim como no Go.


Curiosamente, nos documentos do Mojo, afirma-se que as implementações padrão ainda 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 que satisfazem uma interface, e não em implementá -la.


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 Goose que implementa Quackable :


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


E aqui, para chamar make_it_quack tanto no Goose quanto no Duck (lembre-se, você precisa da função main no Mojo como ponto de entrada para o seu programa):


 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 Quackable para a função make_it_quack , digamos StealthCow , o programa não será compilado:


 @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 quack de Goose ; aqui, obtemos um erro descritivo agradável:


 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 Duck terá que implementar tanto quack quanto make_sound para estar em conformidade com a característica 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 interface{} 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 fn do tipo Mojo.


Sua estrutura deve implementar pelo menos um dos métodos de ciclo de vida como __len__ ou __str__ , que neste caso, o tornaria compatível com Sized ou Stringable , para ser usado com funções que aceitam tipos de características.


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 def mais flexível e, em seguida, aplicar a magia usual do Python para trabalhar com desconhecidos. tipos.


As funções mais estritas do Mojo fn também funcionam em estruturas genéricas usando tipos como DynamicVector ; leia mais sobre isso 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.