paint-brush
Mojo の特性: Go インターフェイスとの比較は?@a2svior
1,846 測定値
1,846 測定値

Mojo の特性: Go インターフェイスとの比較は?

Valentin Erokhin6m2023/12/26
Read on Terminal Reader

長すぎる; 読むには

Mojo トレイトの操作に関するチュートリアルと例、および Mojo トレイトと Go インターフェイスの比較。 Mojo の特性の制限と注意事項。
featured image - Mojo の特性: Go インターフェイスとの比較は?
Valentin Erokhin HackerNoon profile picture
0-item

最近Mojoでトレイトが紹介されたので試してみようと思いました。現在、組み込みの特性には、 CollectionElementCopyableDestructableIntableMovableSizedStringableが含まれます (これらの命名規則では、接尾辞 "-able" が必要のようです!)。

トレイトは構造体で機能します。特性を実装するには、その特性に一致するメソッドを構造体に追加するだけです。次に、特性名をパラメータとして渡します。

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


Mojo の@valueデコレーターは__init__()__copyinit__()__moveinit__()などのライフサイクル メソッドを構造体に挿入し、自分で追加する必要がないため、作業が少し簡素化されます。


Mojo のトレイトはメソッドのデフォルト実装をまだサポートしていないため、上記のQuackableメソッド本体の...サポートされています。 passを使用することもできます。これにより、Python と同様の効果が得られます。

Mojo の特性と Go インターフェイス

名前は異なりますが、Mojo のトレイトに対する基本的なアプローチは Go インターフェイスを思い出させます。 Go では、次のようにDuck構造体を定義し、 Quackableインターフェイスを実装します。


 type Quackable interface { quack() }


そして、このインターフェースを満たす構造体を作成するには、次のようにします。


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


Mojo の実装と比較してください。


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


Mojo バージョンはさらに簡潔で、 Duck構造体型内にquack()メソッド定義をネストしていると思います。構造体での作業に加えて、Mojo のトレイトには Go と同様に、 implementsキーワードが必要ありません。


興味深いことに、Mojo のドキュメントでは、デフォルトの実装はまだ許可されていない、つまり将来的には許可される可能性があると指摘されています。これは、Mojo が、インターフェースを実装するのではなく、インターフェースを満たす構造体に重点を置く Go とは異なるアプローチを追求する可能性があることを意味します。


Go ではデフォルトのメソッド実装は必要ありません。インターフェイス/トレイト実装は単に Go に存在しない概念であるため、埋め込みでも同様の効果が得られます。 Mojo では状況が異なる場合があります。

Mojoの特性の使用方法

特性とインターフェイスの価値は、コードを再利用可能にすることです。たとえば、Mojo では、特性タイプを受け入れる関数を作成できます。


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


そして、同じトレイトを実装するさまざまな構造体を入力として渡します。すべてが正常に機能します。たとえば、これはQuackableを実装するGoose構造体です。


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


ここで、 GooseDuck両方でmake_it_quack呼び出すには (プログラムへのエントリ ポイントとして Mojo のmain関数が必要であることに注意してください):


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


これの出力は次のようになります


Quack! Honk!


特性エラー

Quackable特性を実装していないもの、たとえばStealthCow make_it_quack関数に渡そうとすると、プログラムはコンパイルされません。


 @value struct StealthCow(): pass


 make_it_quack(StealthCow())


以下のエラー メッセージはもっと説明的だったかもしれません。おそらく Mojo チームがそれを改善してくれるでしょうか?


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


Gooseからquackメソッドを削除した場合も同じです。ここでは、わかりやすい説明的なエラーが表示されます。


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


純粋な Python アプローチに対するこの種の静的型チェックの利点は、エラーを簡単にキャッチでき、コードが単純にコンパイルされないため、本番環境へのミスの送信を回避できることです。

継承

Mojo のトレイトはすでに継承をサポートしているため、「Quackable」トレイトは次のように「Audible」トレイトを拡張できます。


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


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


これは、 Duck構造体がQuackable特性に準拠するためにquackmake_soundの両方を実装する必要があることを意味します。


これは、Go の「インターフェイスの埋め込み」の概念に似ています。他のインターフェイスから継承する新しいインターフェイスを作成するには、次のように親インターフェイスを埋め込みます。


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

静的メソッド

特性は、構造体のインスタンスを作成せずに機能する静的メソッドも受け入れます。


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


次のように静的メソッドを呼び出します。


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


これは出力します:


 Quack! Honk! Swimming


最後のメソッド呼び出しでは、Duck インスタンスが作成されないことに注意してください。これは、オブジェクト指向プログラミングとは多少異なりますが、Python の静的メソッドがどのように動作するかです。 Mojo は、この Python 機能に基づいて構築されています。

Go インターフェイスと比較した Mojo トレイトの制限

興味深いことに、空のinterface{}を使用した Go トリックは、任意の型を渡すことができ、Go ジェネリックが導入される前に Go コミュニティで人気がありましたが、Mojo 型関数fnでは機能しません。{}


構造体は、 __len____str__などのライフサイクル メソッドの少なくとも 1 つを実装する必要があります。この場合、構造体はSizedまたはStringableに準拠し、特性タイプを受け入れる関数で使用されます。


これは実際には本当の制限ではなく、Mojo では非常に理にかなっています。Mojo では、動的型付けが必要な場合は、より柔軟なdef関数にフォールバックし、通常の Python マジックを適用して未知の関数を処理できるからです。種類。


より厳密な Mojo fn関数は、 DynamicVectorのような型を使用する汎用構造体でも動作します。詳細については、こちらをご覧ください。


私が見たもう 1 つの制限は、特性ではなく構造体とそのメソッドに関係しており、クリーン アーキテクチャの実装やコードをさまざまな部分に分離する際の潜在的な障害となります。


Go と Mojo の構造体メソッド定義を使用した前述の例の 1 つを考えてみましょう。


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


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


Mojo の例では、より Python に似た構文に従い、メソッド定義を構造体の内部に直接入れ子にしていますが、Go バージョンではメソッド定義を構造体自体から分離できます。このバージョンでは、多くの型とメソッドを含む非常に長い構造体がある場合、多少読みにくくなります。


ただし、これは決定的な違いではなく、注意すべき点です。


Mojo の特性は、この言語がまだ初期段階にあるにもかかわらず、すでにかなり便利になっています。 Go の哲学はシンプルさを重視し、機能を最小限に抑えようとしますが、将来的には Mojo トレイトにさらに多くの機能が追加され、さらに強力になり、さまざまなユースケースが可能になる可能性があります。