Go에 제네릭이없는 이유는 무엇입니까?


126

면책 조항 : 지금까지 바둑을 사용하는 것은 하루 밖에되지 않았으므로 많이 놓쳤을 가능성이 높습니다.

Go에서 generics / templates / whatsInAName에 대한 실제 지원이없는 이유를 아는 사람이 있습니까? 따라서 일반 map이지만 컴파일러에서 제공하는 반면 Go 프로그래머는 자신의 구현을 작성할 수 없습니다. Go를 가능한 한 직각으로 만드는 것에 대한 모든 이야기에서 왜 제네릭 유형을 사용할 수 있지만 새 유형을 만들 수 없습니까?

특히 함수형 프로그래밍과 관련하여 람다, 심지어 클로저도 있지만 제네릭이없는 정적 유형 시스템에서는 어떻게 작성 filter(predicate, list)합니까? interface{}좋습니다. 연결된 목록 등은 유형 안전성 을 희생 하여 수행 할 수 있습니다 .

SO / Google에 대한 빠른 검색은 통찰력을 나타내지 않았기 때문에 제네릭이 나중에 Go에 추가 될 것 같습니다. 나는 톰슨이 자바보다 더 잘할 것이라고 믿지만 왜 제네릭을 배제 하는가? 아니면 계획되어 있고 아직 구현되지 않았습니까?


지적 할 가치가 있다고 생각합니다. interface {}를 사용한다고해서 타입 안전성이 희생되지는 않습니다. 유형이며 다른 유형으로 어설 션 (캐스트 아님) 할 수 있지만 이러한 어설 션은 여전히 ​​유형 안전성을 유지하기 위해 런타임 검사를 호출합니다.
cthom06 2010 년

12
interface{}정적 유형 안전성을 희생합니다 . 그러나 Scheme은 일반적으로 정적 유형 검사가 없기 때문에 Scheme이 다음 단락이라고 언급 할 때 다소 이상한 불만입니다.
poolie 2010

@poolie : 제가 염려하는 것은 언어 내에서 하나의 패러다임을 고수하는 것입니다. 나는 정적 유형 안전 XOR을 사용하고 있지 않습니다.

2
btw는 golang.org에서 볼 수 있듯이 'GO'가 아니라 'Go'로 철자됩니다. 그리고 대소 문자를 구분합니다. :-)
poolie

답변:


78

이 답변은 여기에서 찾을 수 있습니다 : http://golang.org/doc/faq#generics

Go에 일반 유형이없는 이유는 무엇입니까?

제네릭은 어느 시점에서 추가 될 수 있습니다. 일부 프로그래머는 이해하지만 우리는 그들에게 긴급함을 느끼지 않습니다.

제네릭은 편리하지만 유형 시스템과 런타임에서 복잡성이 발생합니다. 우리는 계속 생각하지만 복잡성에 비례하는 가치를 제공하는 디자인을 아직 찾지 못했습니다. 한편 Go에 내장 된 맵과 슬라이스, 빈 인터페이스를 사용하여 컨테이너를 구성하는 기능 (명시 적 언 박싱 포함)은 많은 경우 제네릭이 가능하게하는 코드를 작성하는 것이 가능하지 않다는 것을 의미합니다.

이것은 여전히 ​​미해결 문제입니다.


14
@amoebe, "빈 인터페이스", 철자 interface{}가 가장 기본적인 인터페이스 유형이며 모든 객체가이를 제공합니다. 그것들을 담는 컨테이너를 만들면 모든 (비 원시적) 객체를 받아 들일 수 있습니다. 따라서 ObjectsJava에 보관 된 컨테이너와 매우 유사합니다 .
poolie

4
@YinWang Generics는 유형 추론 환경에서 그렇게 간단하지 않습니다. 더 중요한 것은; interface {}는 C의 void * 포인터와 동일하지 않습니다. 더 나은 비유는 C #의 System.Object 또는 Objective-C의 id 유형입니다. 유형 정보는 보존되며 구체적인 유형으로 다시 "캐스트"(실제로 어설 션됨) 될 수 있습니다. 자세한 내용은 여기에서 확인하세요 : golang.org/ref/spec#Type_assertions
tbone 2013-08-25

2
@tbone C #의 System.Object (또는 Java의 Object 자체)는 본질적으로 "C의 void 포인터"(해당 언어에서 포인터 산술을 수행 할 수없는 부분은 무시 함)가 의미하는 바입니다. 정적 유형 정보가 손실되는 곳입니다. 런타임 오류가 발생하기 때문에 캐스트는별로 도움이되지 않습니다.
Ian

1
@ChristopherPfohl D의 템플릿은 컴파일 시간 오버 헤드가 상당히 적은 것으로 보이며 일반적으로 템플릿을 사용하여 일반적으로 수행하는 것보다 더 많은 코드를 생성하지 않습니다 (사실 상황에 따라 더 적은 코드로 끝날 수 있음 ).
Cubic

3
@ChristopherPfohl Java 제네릭에만 기본 유형에 대한 권투 / 개봉 문제가 있다고 생각합니까? C #의 reified generic에는 문제가 없습니다.
ca9163d9 2014 년

32

이동 2

https://blog.golang.org/go2draft 에는 제네릭에 대한 초안 디자인이 있습니다 .

이동 1

러스 콕스의 이동 베테랑의 하나는 쓴 일반 딜레마를받을 블로그 게시물 그가 요구하는을,

… 느린 프로그래머, 느린 컴파일러 및 비대해진 바이너리 또는 느린 실행 시간을 원하십니까?

느린 프로그래머는 제네릭이없는 결과이고 느린 컴파일러는 제네릭과 같은 C ++로 인해 발생하며 느린 실행 시간은 Java가 사용하는 boxing-unboxing 접근 방식에서 비롯됩니다.

블로그에 언급되지 않은 네 번째 가능성은 C # 경로입니다. C ++와 같이 특수 코드를 생성하지만 필요할 때 런타임에 생성합니다. 정말 좋아하지만 Go는 C #과 매우 다르기 때문에 전혀 적용 할 수 없습니다.

나는의 기술처럼 인기있는 자바 1.4를 사용하여 언급해야 이동의 제네릭 프로그래밍 것과 캐스트 interface{}와 똑같은 문제를 겪고 복싱 - 언 박싱 컴파일시 타입 안전의 손실 외에, (즉, 우리가하고있는 일이기 때문에). 작은 유형 (예 : int)의 경우 Go는 interface{}유형을 최적화하여 interface {}로 캐스트 된 int 목록이 연속적인 메모리 영역을 차지하고 일반 int의 두 배만 차지하도록합니다. 그래도에서 캐스팅하는 동안 런타임 검사의 오버 헤드가 있습니다 interface{}. 참조 .

일반 지원을 추가하는 모든 프로젝트 (여러 개가 있고 모두 흥미 롭습니다)는 컴파일 타임 코드 생성의 C ++ 경로를 균일하게 사용합니다.


이 딜레마에 대한 나의 해결책은 프로그램을 프로파일 링하고 "느린 컴파일러 및 부풀린 바이너리"모드에서 성능에 가장 민감한 부분을 재 컴파일하는 옵션을 사용하여 기본적으로 "느린 실행 시간"으로 이동하는 것입니다. 실제로 그런 것을 구현하는 사람들이 C ++ 경로를 사용하는 경향이 있다는 것은 안타깝습니다.
user7610

1
그것은에 저장되는 작은 유형 (예 : int)에 언급 된 []interface{}사용으로 RAM을 2 배 []int. 사실이지만 더 작은 유형 (예 : 바이트)도 []byte.
BMiner

실제로 C ++ 접근 방식에는 딜레마가 없습니다. 프로그래머가 템플릿 코드를 작성하기로 선택하면 그렇게하는 이점이 느린 컴파일 비용을 압도 할 것입니다. 그렇지 않으면 그는 단지 예전 방식으로 할 수 있습니다.
John Z. Li

딜레마는 어떤 접근 방식을 선택해야 하는가입니다. C ++ 접근 방식으로 딜레마를 해결하면 딜레마가 해결됩니다.
user7610

9

제네릭이 현재 내장되어 있지는 않지만, 코드를 생성하는 작은 유틸리티와 함께 ​​주석을 사용하는 go 용 제네릭의 여러 외부 구현이 있습니다.

다음은 이러한 구현 중 하나입니다. http://clipperhouse.github.io/gen/


1

사실에 따르면, 게시물 :

많은 사람들이 Go 팀의 입장이“Go will never have generics”라고 (잘못) 결론을 내 렸습니다. 반대로 우리는 Go를 훨씬 더 유연하고 강력하게 만들고 Go를 훨씬 더 복잡하게 만드는 잠재적 제네릭이 가지고 있다는 것을 이해합니다. 제네릭을 추가하려면 최대한 적은 복잡성으로 최대한의 유연성과 성능을 얻을 수있는 방식으로 수행해야합니다.


-1

Go 2에 대해 파라 메트릭 다형성 (제네릭)고려 중입니다 .

이 접근 방식은 형식 매개 변수에 대한 제약 조건을 표현하는 데 사용할 수 있는 계약 개념을 도입합니다 .

contract Addable(a T) {
  a + a // Could be += also
}

그런 다음 이러한 계약을 다음과 같이 사용할 수 있습니다.

func Sum(type T Addable)(l []T) (total T) {
  for _, e := range l {
    total += e
  }
  return total
}

이것은 이 단계에서 의 제안 입니다.


귀하의 filter(predicate, list)기능이 같은 유형의 매개 변수를 사용하여 구현 될 수있다 :

func Filter(type T)(f func(T) bool, l []T) (result []T) {
  for _, e := range l {
    if f(e) {
      result = append(result, e)
    }
  }
  return result
}

이 경우를 구속 할 필요가 없습니다 T.


1
오늘이 답변을 읽고 있다면 제안 초안에서 계약 이 삭제되었습니다. go.googlesource.com/proposal/+/refs/heads/master/design/…
jub0bs
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.