Go v1.18 제네릭 사용해보기
15731 단어 go
이동 전 v1.18
이전 버전의 Go에는 제네릭이 없었습니다. 그러나 이전 버전의 Go에서 제네릭에 대한 한 가지 해결 방법은 유형 어설션 및 리플렉션과 함께 빈 인터페이스interface{}
를 사용하는 것입니다. 예를 들어, 모든 유형의 배열을 취하고 배열을 합산하는 함수를 작성하고 싶다고 가정해 보겠습니다. 이전 버전의 Go에서는 다음과 같이 해야 합니다.
func sumAnyType(i interface{}) (o interface{}) {
value := reflect.ValueOf(i)
if reflect.TypeOf(i).Kind() != reflect.Slice || value.Len() < 1 {
return
}
switch value.Index(0).Kind() {
case reflect.Int:
var res int = 0
for i := 0; i < value.Len(); i++ {
res += value.Index(i).Interface().(int)
}
return res
case reflect.Float64:
var res float64 = 0
for i := 0; i < value.Len(); i++ {
res += value.Index(i).Interface().(float64)
}
return res
}
return
}
func main() {
log.Println(sumAnyType([]int{1, 2, 3})) // prints 6
log.Println(sumAnyType([]float64{1.2, 2.5, 3.9})) // prints 7.6
log.Println(sumAnyType([]float32{1.2, 2.5, 3.9})) // prints nil
}
위의 방법이 작동하지만 몇 가지 단점이 있는데 그 중 하나는 코드 반복입니다. 각 데이터 유형에 대한 합산 논리를 작성하고 있으며 특정 데이터 유형에 대한 논리를 작성하지 않으면 해당 유형의 배열을 합산할 수 없습니다.
Go v1.18 제네릭
최신 버전의 Go에 제네릭이 도입되면서 위의 예를 다음과 같이 단순화할 수 있습니다.
func sumAnyType[T int | float64](i []T) (o T) {
for _, v := range i {
o += v
}
return
}
func main() {
log.Println(sumAnyType([]int{1, 2, 3})) // prints 6
log.Println(sumAnyType([]float64{1.2, 2.5, 3.9})) // prints 7.6
// log.Println(sumAnyType([]float32{1.2, 2.5, 3.9})) // does not compile
}
위에서 볼 수 있듯이 Go 제네릭은 각 데이터 유형에 대해 동일한 논리를 다시 작성할 필요 없이 여러 데이터 유형에 대해 동일한 함수를 사용할 수 있으므로 코드를 크게 단순화하고 DRY(반복 금지) 원칙을 적용합니다.
이제 Go에서 제네릭의 중요성을 이해했으므로 Go v1.18에서 제네릭에 도입된 다양한 기능을 이해해 보겠습니다.
1. any
키워드any
키워드는 빈 인터페이스인 interface{}
에 대한 별칭일 뿐입니다. 즉, interface{}
로 할 수 있는 모든 작업은 any
로도 할 수 있습니다. 예를 들어:
func printType(i any) {
_, ok := i.(string)
if ok {
log.Println("its a string")
}
_, ok = i.(int)
if ok {
log.Println("its a integer")
}
}
2. 유형 매개변수, 유형 인수 및 유형 제약
유형 매개변수는 함수가 제네릭이 되도록 허용하여 다른 유형의 인수와 함께 작동할 수 있도록 합니다. 유형 인수와 일반 함수 인수를 사용하여 함수를 호출합니다. 유형 제약 조건은 함수가 받을 수 있는 유형 목록을 정의합니다.
3. comparable
유형 비용 제약comparable
는 ==
또는 !=
를 사용하여 비교할 수 있는 유형을 나타내는 Go v1.18의 사전 선언된 유형 제약 조건입니다.
4. 인터페이스로서의 타입 제약
형식 제약 조건은 재사용할 수 있도록 인터페이스로 선언할 수도 있습니다. 예를 들어:
type CustomConstraint interface {
int | float64
}
func addOne[T CustomConstraint](i T) (o T) {
o = i + 1
return
}
func multiply2[T CustomConstraint](i T) (o T) {
o = i * 2
return
}
func main() {
log.Println(addOne[int](1))
log.Println(multiply2[float64](3.14))
}
5. ~
키워드
기본 유형이 제약 조건과 동일한 모든 사용자 정의 유형을 제한하기 위해 유형 제약 조건에 접두사~
를 붙일 수 있습니다.
CustomConstraint
는 유형int
만 제한하기 때문에 다음은 컴파일되지 않습니다.
type CustomInt int
type CustomConstraint interface {
int
}
func addOne[T CustomConstraint](i T) (o T) {
o = i + 1
return
}
func main() {
log.Println(addOne[CustomInt](1))
}
다음은 CustomConstraint
가 int
및 기본 유형이 int
인 다른 모든 사용자 정의 유형을 제한하기 때문에 성공적으로 컴파일됩니다.
type CustomInt int
type CustomConstraint interface {
~int
}
func addOne[T CustomConstraint](i T) (o T) {
o = i + 1
return
}
func main() {
log.Println(addOne[CustomInt](1))
}
결론적으로 Go 제네릭은 특히 DRY를 시행하는 데 정말 유용하며 다양한 사용 사례에 사용할 수 있습니다. 제 생각에는 Go의 제네릭은 Java와 같은 다른 언어에 비해 더 간단하고 이해하기 쉽습니다.
추가 리소스:
func sumAnyType(i interface{}) (o interface{}) {
value := reflect.ValueOf(i)
if reflect.TypeOf(i).Kind() != reflect.Slice || value.Len() < 1 {
return
}
switch value.Index(0).Kind() {
case reflect.Int:
var res int = 0
for i := 0; i < value.Len(); i++ {
res += value.Index(i).Interface().(int)
}
return res
case reflect.Float64:
var res float64 = 0
for i := 0; i < value.Len(); i++ {
res += value.Index(i).Interface().(float64)
}
return res
}
return
}
func main() {
log.Println(sumAnyType([]int{1, 2, 3})) // prints 6
log.Println(sumAnyType([]float64{1.2, 2.5, 3.9})) // prints 7.6
log.Println(sumAnyType([]float32{1.2, 2.5, 3.9})) // prints nil
}
최신 버전의 Go에 제네릭이 도입되면서 위의 예를 다음과 같이 단순화할 수 있습니다.
func sumAnyType[T int | float64](i []T) (o T) {
for _, v := range i {
o += v
}
return
}
func main() {
log.Println(sumAnyType([]int{1, 2, 3})) // prints 6
log.Println(sumAnyType([]float64{1.2, 2.5, 3.9})) // prints 7.6
// log.Println(sumAnyType([]float32{1.2, 2.5, 3.9})) // does not compile
}
위에서 볼 수 있듯이 Go 제네릭은 각 데이터 유형에 대해 동일한 논리를 다시 작성할 필요 없이 여러 데이터 유형에 대해 동일한 함수를 사용할 수 있으므로 코드를 크게 단순화하고 DRY(반복 금지) 원칙을 적용합니다.
이제 Go에서 제네릭의 중요성을 이해했으므로 Go v1.18에서 제네릭에 도입된 다양한 기능을 이해해 보겠습니다.
1.
any
키워드any
키워드는 빈 인터페이스인 interface{}
에 대한 별칭일 뿐입니다. 즉, interface{}
로 할 수 있는 모든 작업은 any
로도 할 수 있습니다. 예를 들어:func printType(i any) {
_, ok := i.(string)
if ok {
log.Println("its a string")
}
_, ok = i.(int)
if ok {
log.Println("its a integer")
}
}
2. 유형 매개변수, 유형 인수 및 유형 제약
유형 매개변수는 함수가 제네릭이 되도록 허용하여 다른 유형의 인수와 함께 작동할 수 있도록 합니다. 유형 인수와 일반 함수 인수를 사용하여 함수를 호출합니다. 유형 제약 조건은 함수가 받을 수 있는 유형 목록을 정의합니다.
3.
comparable
유형 비용 제약comparable
는 ==
또는 !=
를 사용하여 비교할 수 있는 유형을 나타내는 Go v1.18의 사전 선언된 유형 제약 조건입니다.4. 인터페이스로서의 타입 제약
형식 제약 조건은 재사용할 수 있도록 인터페이스로 선언할 수도 있습니다. 예를 들어:
type CustomConstraint interface {
int | float64
}
func addOne[T CustomConstraint](i T) (o T) {
o = i + 1
return
}
func multiply2[T CustomConstraint](i T) (o T) {
o = i * 2
return
}
func main() {
log.Println(addOne[int](1))
log.Println(multiply2[float64](3.14))
}
5.
~
키워드기본 유형이 제약 조건과 동일한 모든 사용자 정의 유형을 제한하기 위해 유형 제약 조건에 접두사
~
를 붙일 수 있습니다.CustomConstraint
는 유형int
만 제한하기 때문에 다음은 컴파일되지 않습니다.type CustomInt int
type CustomConstraint interface {
int
}
func addOne[T CustomConstraint](i T) (o T) {
o = i + 1
return
}
func main() {
log.Println(addOne[CustomInt](1))
}
다음은
CustomConstraint
가 int
및 기본 유형이 int
인 다른 모든 사용자 정의 유형을 제한하기 때문에 성공적으로 컴파일됩니다.type CustomInt int
type CustomConstraint interface {
~int
}
func addOne[T CustomConstraint](i T) (o T) {
o = i + 1
return
}
func main() {
log.Println(addOne[CustomInt](1))
}
결론적으로 Go 제네릭은 특히 DRY를 시행하는 데 정말 유용하며 다양한 사용 사례에 사용할 수 있습니다. 제 생각에는 Go의 제네릭은 Java와 같은 다른 언어에 비해 더 간단하고 이해하기 쉽습니다.
추가 리소스:
https://teivah.medium.com/when-to-use-generics-in-go-36d49c1aeda
- https://tutorialedge.net/golang/getting-starting-with-go-generics/
Reference
이 문제에 관하여(Go v1.18 제네릭 사용해보기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/mohamadharith/trying-out-go-v118-generics-1hdo텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)