paint-brush
Mojo 특성: Go 인터페이스와 어떻게 비교됩니까?~에 의해@a2svior
1,846 판독값
1,846 판독값

Mojo 특성: Go 인터페이스와 어떻게 비교됩니까?

~에 의해 Valentin Erokhin6m2023/12/26
Read on Terminal Reader

너무 오래; 읽다

예제를 통해 Mojo 특성 작업에 대한 연습 및 Go 인터페이스와 Mojo 특성 비교. Mojo 특성의 제한 사항 및 주의 사항.
featured image - Mojo 특성: Go 인터페이스와 어떻게 비교됩니까?
Valentin Erokhin HackerNoon profile picture
0-item

최근 Mojo에 특성이 도입되었기 때문에 한번 시도해 볼까 생각했습니다. 현재 내장 특성에는 CollectionElement , Copyable , Destructable , Intable , Movable , SizedStringable이 포함됩니다("-able" 접미사는 이러한 명명 규칙에 포함되는 것 같습니다!).

특성은 구조체에서 작동합니다. 특성을 구현하려면 해당 특성과 일치하는 구조체에 메서드를 추가하기만 하면 됩니다. 그런 다음 특성 이름을 매개변수로 전달합니다.

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


Mojo의 @value 데코레이터는 __init__() , __copyinit__()__moveinit__() 와 같은 수명 주기 메서드를 구조체에 삽입하여 직접 추가할 필요가 없으므로 생활을 조금 단순화합니다.


Mojo의 특성은 아직 메소드에 대한 기본 구현을 지원하지 않으므로 위의 Quackable 메소드 본문에 ... 있습니다. Python에서와 동일한 효과를 갖는 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 버전은 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 특성을 구현하지 않는 것을 make_it_quack 함수에 전달하려고 하면(예 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


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 특성의 한계

흥미롭게도, 모든 유형을 전달할 수 있고 Go Generics가 도입되기 전에 Go 커뮤니티에서 인기가 있었던 빈 interface{} 사용하는 Go 트릭은 Mojo 유형 함수 fn 에서는 작동하지 않습니다.


구조체는 __len__ 또는 __str__ 과 같은 수명 주기 메서드 중 하나 이상을 구현해야 합니다. 이 경우 특성 유형을 허용하는 함수와 함께 사용하려면 Sized 또는 Stringable 준수해야 합니다.


이는 실제로 실제 제한 사항이 아니며 Mojo에서 많은 의미가 있습니다. Mojo를 사용하면 동적 타이핑이 필요한 경우 더 유연한 def 함수로 되돌아간 다음 일반적인 Python 마법을 적용하여 알려지지 않은 작업을 수행할 수 있기 때문입니다. 유형.


보다 엄격한 Mojo fn 함수는 DynamicVector 와 같은 유형을 사용하는 일반 구조체에서도 작동합니다. 자세한 내용은 여기를 참조하세요.


내가 본 또 다른 제한은 특성보다는 구조체 및 그 메서드와 관련이 있으며 클린 아키텍처를 구현하거나 코드를 다른 부분으로 분리하는 데 잠재적인 장애물입니다.


Go 대 Mojo 구조체 메서드 정의가 포함된 이전 예제 중 하나를 고려하세요.


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


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


Python과 유사한 구문을 따르는 Mojo 예제는 메소드 정의를 구조체 내부에 직접 중첩하는 반면 Go 버전에서는 이를 구조체 자체와 분리할 수 있습니다. 이 버전에서는 유형과 메소드가 많은 매우 긴 구조체가 있는 경우 읽기가 다소 어려울 것입니다.


하지만 중요한 차이점은 아니며 단지 알아두어야 할 사항입니다.


Mojo 특성은 언어가 초기 단계임에도 불구하고 이미 꽤 유용합니다. Go 철학은 단순성에 관한 것이며 기능을 최소한으로 유지하려고 시도하지만 앞으로는 Mojo 특성에 더 많은 기능이 추가되어 더욱 강력해지고 다양한 사용 사례가 가능해질 가능성이 높습니다.