👨💻 [Go] Go 에서 제네릭 프로그래밍 이란 무엇이며, 어떻게 활용할 수 있나요?
"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."
👨💻 안녕하세요, 윌리 프로젝트의 윌리 입니다.
Go 에서 제네릭 프로그래밍 이란 무엇이며, 어떻게 활용할 수 있는지 간략히 알아보았습니다.
질문 >
Go 에서 제네릭 프로그래밍 이란 무엇이며, 어떻게 활용할 수 있나요?
답변 >
Go에서 제네릭 프로그래밍(Generic Programming)은 함수나 데이터 구조가 특정 타입에 의존하지 않고 다양한 타입을 처리할 수 있도록 설계하는 프로그래밍 기법입니다. 이를 통해 코드 재사용성을 높이고 반복적인 코드를 줄일 수 있습니다.
Go는 1.18 버전에서 제네릭을 지원하기 시작했습니다. 제네릭은 타입 파라미터를 사용하여 타입 안정성을 유지하면서도 다양한 타입을 처리할 수 있도록 합니다.
제네릭의 주요 특징
- 타입 파라미터 사용: 타입을 함수나 데이터 구조의 매개변수로 전달합니다.
- 컴파일 타임 타입 안정성: 실행 전에 타입 검사를 수행하여 오류를 방지합니다.
- 코드 재사용성: 여러 타입에 대해 동일한 로직을 작성할 수 있습니다.
기본 문법
1. 타입 파라미터 정의
타입 파라미터는 대괄호([])를 사용하여 정의하며, 일반적으로 T와 같은 단일 문자 이름을 사용합니다.
func 함수명[T any](param T) T {
// 제네릭 로직
}
2. 여러 타입 파라미터 정의
func 함수명[T, U any](param1 T, param2 U) {
// 제네릭 로직
}
3. 제네릭 데이터 구조 정의
type 구조체명[T any] struct {
Field T
}
제네릭 활용 예제
1. 제네릭 함수
package main
import "fmt"
// 제네릭 함수 정의
func Swap[T any](a, b T) (T, T) {
return b, a
}
func main() {
x, y := 10, 20
fmt.Println("Before:", x, y)
x, y = Swap(x, y)
fmt.Println("After:", x, y)
a, b := "hello", "world"
fmt.Println("Before:", a, b)
a, b = Swap(a, b)
fmt.Println("After:", a, b)
}
실행 결과:
Before: 10 20
After: 20 10
Before: hello world
After: world hello
2. 제네릭 데이터 구조
package main
import "fmt"
// 제네릭 구조체 정의
type Stack[T any] struct {
items []T
}
// Push 메서드
func (s *Stack[T]) Push(item T) {
s.items = append(s.items, item)
}
// Pop 메서드
func (s *Stack[T]) Pop() T {
item := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return item
}
func main() {
intStack := Stack[int]{}
intStack.Push(10)
intStack.Push(20)
fmt.Println(intStack.Pop()) // 20
fmt.Println(intStack.Pop()) // 10
stringStack := Stack[string]{}
stringStack.Push("Go")
stringStack.Push("Generics")
fmt.Println(stringStack.Pop()) // Generics
fmt.Println(stringStack.Pop()) // Go
}
실행 결과:
20
10
Generics
Go
3. 제네릭 타입 제한과 함께 사용
package main
import (
"constraints"
"fmt"
)
// 숫자 타입 제한
func Sum[T constraints.Ordered](a, b T) T {
return a + b
}
func main() {
fmt.Println(Sum(10, 20)) // 정수
fmt.Println(Sum(1.5, 2.3)) // 실수
}
실행 결과:
30
3.8
any 키워드
any는 빈 인터페이스(interface{})의 별칭으로, 모든 타입을 허용합니다. 제네릭에서 타입 제한이 필요 없을 때 사용합니다.
func Print[T any](value T) {
fmt.Println(value)
}
타입 제한(Type Constraints)
제네릭 함수나 타입에 특정 타입 집합만 허용하도록 제한할 수 있습니다. Go는 constraints 패키지를 통해 기본적인 타입 제한을 제공합니다.
- constraints.Ordered: 정렬 가능한 타입 (예: int, float, string)
- 사용자 정의 타입 제한: 인터페이스를 사용하여 커스텀 타입 제한을 정의할 수 있습니다.
type Number interface {
int | float64
}
func Multiply[T Number](a, b T) T {
return a * b
}
실용적인 활용 예제
1. 최소값 및 최대값 찾기
package main
import (
"constraints"
"fmt"
)
func Min[T constraints.Ordered](a, b T) T {
if a < b {
return a
}
return b
}
func Max[T constraints.Ordered](a, b T) T {
if a > b {
return a
}
return b
}
func main() {
fmt.Println(Min(3, 5)) // 3
fmt.Println(Max(3.2, 2.8)) // 3.2
fmt.Println(Min("apple", "banana")) // apple
}
실행 결과:
3
3.2
apple
2. 제네릭 맵 변환 함수
package main
import "fmt"
func Map[T any, U any](input []T, f func(T) U) []U {
result := make([]U, len(input))
for i, v := range input {
result[i] = f(v)
}
return result
}
func main() {
numbers := []int{1, 2, 3, 4}
squared := Map(numbers, func(x int) int {
return x * x
})
fmt.Println(squared) // [1 4 9 16]
}
제네릭의 장점
- 코드 재사용: 여러 타입에 대해 동일한 로직을 중복 작성할 필요가 없습니다.
- 타입 안정성: 런타임에 타입 관련 오류가 발생하지 않고, 컴파일 타임에 잡아냅니다.
- 가독성 향상: 중복 코드를 제거하여 더 깔끔한 코드를 작성할 수 있습니다.
주의 사항
- 복잡성 관리: 제네릭을 과도하게 사용하면 코드가 복잡해질 수 있습니다.
- 성능: Go의 제네릭은 컴파일 시 타입을 구체화하므로 런타임 성능에는 큰 영향을 미치지 않지만, 코드 크기가 증가할 수 있습니다.
- 타입 제한 사용: 필요하지 않은 경우에는 과도한 타입 제한을 피하는 것이 좋습니다.
Go의 제네릭 프로그래밍은 유연성과 안전성을 동시에 제공하며, 다양한 데이터 타입을 다루는 함수를 작성할 때 매우 유용합니다. 적절히 활용하면 코드를 효율적이고 간결하게 작성할 수 있습니다.
🎬 유튜브 채널 🎬
위로그@WiLog
📢 안녕하세요, 위로그@WiLog 시청자 여러분, 저는 윌리(Willee) 입니다. 📢 위로그@WiLog 는 자기계발을 목적으로 하는 채널 입니다. 📢 오늘도 즐겁게~ 자신을 위한 계발을 함께 해보아요~ d^_^b 📌
www.youtube.com
🎬 치지직 채널 🎬
위로그 채널 - CHZZK
지금, 스트리밍이 시작됩니다. 치지직-
chzzk.naver.com