Language/Golang

Golang (Go언어) 제네릭 프로그래밍(Generic Programming) 2/3

HeeWorld 2025. 1. 1. 15:04

Tucker의 Go 언어 프로그래밍 책과 유튜브를 통해 학습 중입니다.

Golang 마스코트 Gopher(고퍼)

 

인터페이스(Interface)와 타입 제한자 차이

- 하나의 Interface가 있고, String() 메서드를 가지고 있으면 이는 string 타입들을 다 해당 Interface로 사용될 수 있음.

tpye Stringer interface {
	String() string
}

 

- 타입 제한은, 동일하게 interface라고 사용하지만 틸드(~)를 사용하여 정의된 타입들 중 하나가 가능함.

tpye Integer interface {
	~int8 | ~int16 | ~int32 | int64 | ~int
}

 

→ 같은 키워드인 인터페이스를 동일하게 사용하였지만, 둘 다 타입을 제한하는 목적으로 사용한다고 볼 수 있음.

- 인터페이스는 해당 조건에 정의된 메서드를 포함한 타입만 해당 인터페이스로 가능함. = 타입 제한 목적

- 타입 제한자는 포함된 타입들 중 하나만 가능하다라는 의미. = 타입 제한 목적

 

✓ 엄연하게 둘은 다르기 때문에 쓰임이 완전히 다름.

 

package main

import (
	"fmt"
	"hash/fnv"
)

type ComparableHasher interface {
	comparable // 같다(==)와 다르다(!=) 가능
	Hash() uint32
}

type MyString string

func (s MyString) Hash() uint32 {
	h := fnv.New32a()
	h.Write([]byte(s))
	return h.Sum32()
}

func Equal[T ComparableHasher](a, b T) bool {
	if a == b {
		return true
	}
	return a.Hash() == b.Hash()
}

func main() {
	var s1 MyString = "Hello"
	var s2 MyString = "World"
	fmt.Println(Equal(s1, s2))
}

// 결과

false

 

- ComparableHasher라는 이름 타입 제한을 정의하고, comparable은 ==,!= 를 지원함.

   Go 내부 타입 제한으로 Hash() uint32 메서드를 포함하도록 제한함.

   따라서, 해당 이름 타입은 같다와 같지 않다를 지원하고 Hash() uint32 메서드를 포함한 타입만 가능함.

 

- MyString이란, string 별칭 타입을 정의하고, Hash32 uint32 메서드를 포함하도록 함.

   MyString은 ComparableHasher 제한에 만족한 타입이 됨.

 

- ComparableHasher 제한을 사용하는 Equal()이라는 제네릭 함수를 정의함.

   먼저 == 연산자로 둘이 같은지 확인하고, 만약 다를 경우 Hash() 메서드 호출 결과로 다시 한번 확인해서 같은지 확인함.

 

→ 타입 제한에 일반 인터페이스와 같이 특정 메서드를 포함하도록 제한을 추가할 수 있음.

 

※ 타입 제한은 제네릭 프로그래밍의 타입 파라미터에서만 사용될 수 있고 일반 인터페이스처럼 사용할 수 없음.

func Equal(a, b ComparableHasher) bool { // Error

 

ComparableHasher 타입 제한을 포함하고 있기 때문에 인터페이스 처럼 사용할 수 없어 에러가 발생함.

타입 제한을 포함한 인터페이스는 반드시 타입 파라미터로 정의되어야 함!

func Equal[T ComparableHasher](a, b T) bool { // OK

 

package main

import (
	"fmt"
	"strconv"
	"strings"
)

func Map[F, T any](s []F, f func(F) T) []T {
	rst := make([]T, len(s))
	for i, v := range s {
		rst[i] = f(v)
	}
	return rst
}

func main() {
	doubled := Map([]int{1, 2, 3}, func(v int) int {
		return v * 2
	})
	// 각 값을 두 배씩 증가시키는 슬라이스

	uppered := Map([]string{"hello", "world", "abc"}, func(v string) string {
		return strings.ToUpper(v)
	})
	// 대문자로 변경하는 슬라이스

	tostring := Map([]int{1, 2, 3}, func(v int) string {
		return "str" + strconv.Itoa(v)
	})
	// 문자열로 변경하는 슬라이스

	fmt.Println(doubled)
	fmt.Println(uppered)
	fmt.Println(tostring)
}


// 결과

[2 4 6]
[HELLO WORLD ABC]
[str1 str2 str3]

 

- Map() 제네릭 함수를 정의하고, F와 T 두 개의 타입 파라미터가 있고, 이 모두는 모든 타입을 사용할 수 있음.

  함수 인수 두 개를 받는데 첫 번 째 인수는 F타입 슬라이스이고, 두 번째 인수는 함수 타입으로 F 타입 값을 받아 T 타입 값을 반환

  Map() 함수는 s 슬라이스의 각 요소를 두 번째 인수인 f 함수를 사용해 변환한 새로운 슬라이스를 반환함.

 

- Map() 함수를 이용해 int 슬라이스의 각 값을 두 배씩 증가시키는 새로운 슬라이스를 만듦.

- Map() 함수를 이용해 string 슬라이스의 각 요소를 대문자로 변경하는 새로운 슬라이스를 만듦.

- Map() 함수를 이용해 int 슬라이스의 각 값을 문자열로 변경하는 새로운 슬라이스를 만듦.