Tucker의 Go 언어 프로그래밍 책과 유튜브를 통해 학습 중입니다.
제네릭 프로그래밍(Generic Programming)
- Go 1.18 버전에 추가된 기능으로 타입 파라미터를 통해 하나의 함수나 타입이 여러 타입에 대응해 동작하도록 하는 기법
- 자바, C#, C++ 같은 언어에서는 이미 제공되던 기능임.
package main
import "fmt"
func min(a, b int) int {
if a < b {
return a
}
return b
}
func main() {
var a int = 10
var b int = 20
fmt.Println(min(a, b))
var c int16 = 10
var d int16 = 20
fmt.Println(min(c, d))
}
// 결과
./prog.go:19:18: cannot use c (variable of type int16) as int value in argument to min
./prog.go:19:21: cannot use d (variable of type int16) as int value in argument to min
→ a,b는 출력이 가능하나 c,d는 아래 결과로 보면 int16 타입을 int로 변환할 수 없어서 사용할 수 없다고 나옴.
- min은 int 타입인데, c,d는 int16 타입으로 서로 타입이 다르며, Go는 이를 변환시켜주지 않음(최강 타입 언어).
= 변환하고 싶다면 Println부분에서 int를 사용하여 변환해주면 됨.
var e float32 = 3.14
var f float32 = 3.98
fmt.Println(min(int(e), int(f)))
→ float32 실수의 경우 int로 변환하면 소수점 아래로 탈락하여 e도 3, f도 3이되어 비교가 불가능함.
- 이런 경우에는 실수 타입을 받을 수 있는 Func을 다시 정의 해야함.
package main
import "fmt"
func min(a, b int) int {
if a < b {
return a
}
return b
}
// float32 타입 재정의
func minF32(a, b float32) float32 {
if a < b {
return a
}
return b
}
func main() {
var a int = 10
var b int = 20
fmt.Println(min(a, b))
var c int16 = 10
var d int16 = 20
fmt.Println(min(int(c), int(d)))
var e float32 = 3.14
var f float32 = 3.98
fmt.Println(minF32(e, f))
}
// 결과
10
10
3.14
✓ 이런 경우 타입 별로 func을 만들어줘야 하는 문제가 있어 다른 언어에서는 제네릭을 통해 해결하고 있었음.
1.18 버전에서 제네릭 버전이 추가되어 이를 해결할 수 있게 됨.
제네릭 함수
func funcName[T constaint](p T) {
- funcName: func 함수 키워드를 적고, 그 뒤 함수명 적음(함수 선언)
- [T constaint]: 대괄호를 열고 타입 파라미터를 적음, 파라미터 이름과 타입 제한을 적음.
> T가 파라미터 이름이고, constaint가 타입 제한
> 타입 파라미터는 필요에 따라 컴마(,) 기호로 구분하여 여러 개를 적을 수 있음.
> 소괄호를 열어 일반 함수처럼 입력과 출력을 씀, 타입 파라미터에 사용한 타입 파라미터 이름을 특정 타입대신 사용할 수 있음.
func Print[T any](a, b T) {
fmt.Println(a, b)
}
- Print() 함수는 제네릭 함수로 하나의 타입 파라미터를 가지고 있고, [T any]라고 정의함.
- T는 타입 파라미터 이름이고(일반적으로 대문자 T를 사용함), any는 타입 제한으로 모든 타입이 가능하다는 의미.
- 입력 인수를 받아, 모두 T 타입으로 정의 되어있으며, T 타입은 앞에서 정의한 타입 파라미터이고, 모든 타입이 가능하다는 뜻.
package main
import "fmt"
func print[T any](a T) {
fmt.Println(a)
}
func main() {
var a int = 10
print(a)
var b float32 = 3.14
print(b)
var c string = "Hello"
print(c)
}
// 결과
10
3.14
Hello
타입 제한
func add[T any](a, b T) T {
return a + b
}
- 위 함수의 경우 add() 함수는 T타입 파라미터가 정의되어 있고, T 타입 제한은 any임.
- 따라서, a, b 두 개의 인수는 모든 타입을 할 수 있지만 빌드 에러가 발생함.
→ 모든 타입에서 연산자를 제공하는 것이 아니기 때문에 해당 연산자를 사용할 수 없는 것.
→ 특정 조건을 정의하여 타입에 연산자를 지원하고 있음을 알려줘야 함.
func add[T int8 | int16 | int32 | int64 | int](a, b T) T {
return a + b
}
- 제한자에 연산자를 사용할 수 있는 타입을 기재하면 됨.
ex) int8, int16, int32, int ... 등
- T에는 해당 타입들 중에 하나가 올 수 있고, 가능함을 보여줘야 함.
✓ 매번 조건을 길게 적는 것은 어려움이 있으니, 타입 제한만 따로 정의할 수 있음.
// 타입 제한 선언
type Integer interface {
int8 | int16 | int32 | int54 | int
}
func add[T Integer](a, b, T) T {
return a + b
}
- Integer라는 이름으로 int 타입들을 포함한 타입 제한을 정의하고 add() 제네릭 함수에서 Integer 타입 제한을 사용하고 있음.
- 타입 제한을 정의할 때 interface 키워드를 사용하는데, 이는 정확하게는 인터페이스 처럼 동작하는 것임.
✓ 매번 타입 제한 선언하는 것도 귀찮(?)아서 그런가, 1.18 버전에 이미 정의된 몇 가지 타입 제한을 제공하고 있음.
> https://tip.golang.org/doc/go1.18
- golang.org/x/exp/constraints 패키지를 보면 6개의 제약 조건이 정의되어있는 것을 확인할 수 있음.
(https://pkg.go.dev/golang.org/x/exp/constraints)
package main
import (
"fmt"
"golang.org/x/exp/constraints"
)
func print[T constraints.Ordered](a, b T) T {
if a < b {
return a
}
return b
}
func main() {
var a int = 10
var b int = 20
fmt.Println(min(a, b))
var c int16 = 10
var d int16 = 20
fmt.Println(min(c, d))
var e float32 = 3.14
var f float32 = 3.98
fmt.Println(min(e, f))
}
// 결과
10
10
3.14
- constraints.Ordered에 < <= >= > 비교 연산자 사용이 가능하여 위와 같은 결과를 얻을 수 있음.
* Ordered is a constraint that permits any ordered type: any type that supports the operators < <= >= >. *
타입 제한 더 알아보기
type Signed interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
- 타입 앞에 틸더(~,tilde)가 붙어있는데, 의미는 해당 타입을 기본으로 하는 모든 별칭 타입들까지 포함한다는 의미.
package main
import (
"fmt"
)
type Integer interface {
int | int8 | int16 | int32 | int64
}
func print[T Integer](a, b T) T {
if a < b {
return a
}
return b
}
type MyInt int
func main() {
var a int = 10
var b int = 20
fmt.Println(min(a, b))
var c int16 = 10
var d int16 = 20
fmt.Println(min(c, d))
var e MyInt = 10
var f MyInt = 20
fmt.Println(min(e, f))
}
※ 1.18 버전에서는 에러가 발생하는데, 1.23 버전에서는 틸더(~)를 사용하지 않아도 결과값이 출력 됨.
별도로 릴리즈 노트를 찾아서 확인 필요 ※
- 1.18 버전 기준으로는 틸더(~)를 사용하지 않으면, 에러가 발생하고 Integer의 int 타입에 ~을 사용해야 에러가 발생하지 않음.
- 별칭 타입(MyInt)까지 포함하려고 틸더를 사용하고, 별칭 타입까지 포함한다고 보면 됨.
package main
import (
"fmt"
)
type Integer interface {
~int | int8 | int16 | int32 | int64
}
func print[T Integer](a, b T) T {
if a < b {
return a
}
return b
}
type MyInt int
func main() {
var a int = 10
var b int = 20
fmt.Println(min(a, b))
var c int16 = 10
var d int16 = 20
fmt.Println(min(c, d))
var e MyInt = 10
var f MyInt = 20
fmt.Println(min(e, f))
}
// 결과
10
10
10
'Language > Golang' 카테고리의 다른 글
Golang (Go언어) 제네릭 프로그래밍(Generic Programming) 3/3 (0) | 2025.01.01 |
---|---|
Golang (Go언어) 제네릭 프로그래밍(Generic Programming) 2/3 (0) | 2025.01.01 |
Golang (Go언어) 채널과 컨텍스트(Channel & Context) (0) | 2024.12.11 |
Golang (Go언어) Go루틴 (0) | 2024.12.07 |
Golang (Go언어) 에러 핸들링 (2) | 2024.12.05 |