Черты были введены в Моджо недавно, поэтому я решил их попробовать. В настоящее время встроенные свойства включают , , , , , и (похоже, суффикс «-able» присутствует в этих соглашениях об именах!). Трейты работают со структурами. Чтобы реализовать признак, вы просто добавляете метод в структуру, соответствующую этому признаку; затем передайте имя черты в качестве параметра: CollectionElement Copyable Destructable Intable Movable Sized Stringable @value struct Duck(Quackable): fn quack(self): print("Quack!") Декоратор в Mojo вставляет в структуру методы жизненного цикла, такие как , и , немного упрощая нашу жизнь, поскольку нам не нужно добавлять их самостоятельно. @value __init__() __copyinit__() __moveinit__() Трейты в Mojo пока не поддерживают реализации методов по умолчанию, поэтому в теле метода выше. Вы также можете использовать , который будет иметь тот же эффект, что и в Python. ... Quackable pass Черты 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 еще более лаконична: определение метода вложено в тип структуры . Помимо работы со структурами, трейты в Mojo не требуют ключевого слова , как в Go. quack() Duck implements Интересно, что в документации Mojo отмечается, что реализации по умолчанию не разрешены, а это означает, что они могут быть разрешены в будущем. Это означает, что Mojo может использовать подход, отличный от Go, который фокусируется на структурах, интерфейсу, а не на его . пока удовлетворяющих реализации Реализации методов по умолчанию не нужны в Go, и аналогичный эффект достигается при встраивании, поскольку реализация интерфейса/характеристики — это просто концепция, которой не существует в Go. В Моджо все может быть по-другому. Как использовать черты Моджо Ценность свойств и интерфейсов заключается в возможности повторного использования кода. Например, в Mojo вы можете писать функции, принимающие типы признаков… fn make_it_quack[T: Quackable](could_be_duck: T): could_be_duck.quack() А затем передать в качестве входных данных разные структуры, реализующие ту же черту — все просто будет работать! Например, вот структура , реализующая : Goose Quackable @value struct Goose(Quackable): fn quack(self): print("Honk!") И здесь, чтобы вызвать как в так и в (помните, вам нужна функция в Mojo как точка входа в вашу программу): make_it_quack Goose Duck main def main(): make_it_quack(Duck()) make_it_quack(Goose()) Результатом этого будет Quack! Honk! Ошибки черт Если я попытаюсь передать в функцию что-то, что не реализует свойство , скажем, , программа не скомпилируется: make_it_quack Quackable StealthCow @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 То же самое, если я удалю метод из ; здесь мы получаем красивую описательную ошибку: quack Goose 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 quack make_sound Quackable Это похоже на концепцию «встраивания интерфейса» в 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. Ограничения свойств Mojo по сравнению с интерфейсами Go Интересно, что трюк Go с пустым , который позволяет передавать любой тип и был популярен в сообществе Go до появления Go Generics, не будет работать с функцией типа Mojo. interface{} fn Ваша структура должна реализовать хотя бы один из методов жизненного цикла, например или , что в данном случае приведет ее в соответствие с или для использования с функциями, которые принимают типы признаков. __len__ __str__ Sized Stringable На самом деле это не является реальным ограничением и имеет большой смысл в Mojo, поскольку в Mojo, если вам нужна динамическая типизация, вы можете просто вернуться к более гибкой функции , а затем применить обычную магию Python для работы с неизвестными типы. def Более строгие функции Mojo также работают с универсальными структурами, используя такие типы, как ; подробнее об этом читайте . fn DynamicVector здесь Еще одно ограничение, которое я вижу, связано со структурами и их методами, а не с типажами, и является потенциальным препятствием для реализации чистой архитектуры/разделения кода на разные части. Рассмотрим один из предыдущих примеров с определением метода структуры Go vs. Mojo: 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, что сделает их еще более мощными и позволит использовать множество различных вариантов использования.