Go의 진화: 제네릭 메서드 제안

수년 동안 Go 커뮤니티는 언어의 타입 시스템에서 기묘한 제한 사항을 겪어왔습니다. 함수와 타입은 제네릭할 수 있지만, 메서드는 그렇지 못하다는 점입니다. 여러 타입을 처리하는 메서드가 필요할 경우, 해당 로직을 독립적인 제네릭 함수로 옮겨야만 했으며, 이는 종종 투박한 API와 "janky"한 패키지 구조를 초래했습니다.

이제 새로운 제안은 Go가 메서드를 처리하는 방식에 대한 "관점의 변화"를 제시합니다. Go 팀은 구체적인 메서드(concrete methods)를 인터페이스 구현체로서의 역할로부터 분리함으로써, 마침내 언어에 제네릭 메서드를 도입하고자 합니다.

핵심 문제: 메서드 vs 인터페이스

왜 원래의 제네릭 구현에서 제네릭 메서드가 제외되었는지 이해하려면, Go에서 구체적인 메서드와 인터페이스 사이의 관계를 이해해야 합니다. 역사적으로 메서드의 주요 목적은 인터페이스를 충족하는 것이었습니다.

만약 Go가 구체적인 메서드에 타입 파라미터를 허용한다면(예: func (s *S) DoSomething[T any](val T)), 논리적으로 인터페이스 또한 이를 허용해야 합니다. 하지만 제네릭 인터페이스 메서드를 구현하는 것은 상당한 기술적 과제입니다. Go는 구조적 타이핑(structural typing, 타입이 암시적으로 인터페이스를 구현하는 방식)을 사용하기 때문에, 컴파일러는 인터페이스를 통해 런타임에 호출될 수 있는 제네릭 메서드의 모든 가능한 인스턴스화를 컴파일 타임에 알 수 없습니다.

오랫동안 Go FAQ에 반영된 공식 입장은, 이러한 인터페이스 병목 현상 때문에 언어에 제네릭 메서드가 영원히 추가되지 않을 수도 있다는 것이었습니다.

"관점의 변화"

새로운 제안은 구체적인 메서드가 인터페이스를 구현하는지 여부와 관계없이 그 자체로 유용하다고 주장합니다. 메서드는 다음과 같은 이점을 제공합니다:

  1. 더 나은 조직화: 메서드는 동작하는 데이터와 로직을 직접 연결합니다.
  2. 구문적 유연성: 메서드 체이닝(x.a().b().c())을 가능하게 하며, 이는 중첩된 함수 호출(c(b(a(x))))보다 가독성이 좋습니다.

이 제안은 실용적인 타협안을 제시합니다: 제네릭 구체 메서드는 허용하되, 인터페이스를 충족하는 용도로는 허용하지 않는다.

이 규칙에 따라, 만약 인터페이스가 m(string) 메서드를 정의한다면, 구체 메서드 m[P any](P)는 인터페이스와 일치하지 않습니다. 인터페이스 메서드는 타입 파라미터를 가질 수 없기 때문입니다. 이를 통해 Go 팀은 제네릭 인터페이스 디스패치라는 엄청나게 복잡한 문제를 해결하지 않고도 제네릭 메서드의 구문적, 조직적 이점을 제공할 수 있습니다.

기술적 구현 및 구문

제안된 변경 사항은 구문적으로 최소한입니다. 메서드 선언은 함수 선언을 반영하도록 업데이트될 것입니다:

MethodDecl = "func" Receiver MethodName [ TypeParameters ] Signature [ FunctionBody ]

실용적인 예시

기본 제네릭 메서드:

type S struct { ... }
func (*S) m[P any](x P) { ... }

var s S
s.m[int](42) // Explicit type argument
s.m(x)       // Type inference

제네릭 수신자(Receiver)를 가진 제네릭 메서드:

type G[P any] struct{ ... }
func (*G[P]) m[Q any](x Q) { ... }

컴파일러 영향

제안서는 비인터페이스 수신자를 통한 메서드 호출은 컴파일 타임에 정적으로 해결될 수 있다고 언급합니다. 개념적으로 컴파일러는 이러한 호출을 제네릭 함수 호출로 재작성할 수 있으며, 이는 상당한 런타임 오버헤드 없이 구현을 가능하게 합니다.

커뮤니티 반응: 갈라진 진영

이 발표는 Go 커뮤니티에서 다양한 반응을 불러일으켰습니다. 이는 표현력 있는 기능을 갈망하는 이들과 Go의 극단적인 단순함이라는 원래 철학을 중치하는 이들 사이의 광범위한 긴장감을 반영합니다.

열광론자

많은 개발자가 안도하며, 다른 현대적인 언어에서 온 사용자들에게 이 기능이 중요한 공백을 메워준다고 언급했습니다. 한 사용자는 이것이 "weird janky package APIs"를 제거할 필요를 없애줄 것이라고 말했으며, 다른 이들은 마침내 모나드와 같은 복잡한 패턴을을 구현할 수 있게 된다는 것에 흥분을 표했습니다.

전통론자

반대로, 일부는 이를 Go의 핵심 정체성에서 벗어나는 것으로 봅니다. 비판론자들은 사용자 기대에 부ตาม을 쫓는 것이 "feature creep"로 이어지고, 이러한 복잡성의 추가가 언어의 단순성을 저하시킨다고 주장합니다.

"Go를 위한 슬픈 날입니다. 박사 학위 소지자들이 이겼고, 단순함은 죽었습니다."

결론

제네릭 구체 메서드를 도입함으로써, Go는 이론적 순수성과 개발자 생산성 사이의 간격을 메우려고 시도하고 있습니다. 제네릭 인터페이스 문제를 해결하지는 못하지만, 메서드가 코드를 조직화하는 방식으로서 갖는 유용성이 모든 메서드가 인터페이스 구현체여야 한다는 필요성보다 더 큰 경우가 많다는 점을 인정하는 것입니다. 만약 채택된다면, 이 변화는 개발자들이 제네릭 로직을 패키지 수준의 함수에서 다시 타입 내부로 옮기면서 라이브러리 리팩토링의 물결을 이고올 것입니다.

Sources