grep

Engineering

Go 제네릭

NHN

2022년 4월 15일

원문에서 보기 ↗

안녕하세요. 최근에 맡은 프로젝트에서 Go 언어를 사용하여 개발을 진행하고 있습니다. Go 언어의 장점은 간결한 문법, 컨벤션 통일, 가벼운 고루틴(Java의 쓰레드) 등이 있고, Docker 역시 Go 언어로 개발되어 있을 정도로 많은 곳에서 사용 중입니다. 2022년 3월에 Go 1.18이 릴리즈되면서 제네릭이 새롭게 추가된다고 하여 예제를 중심으로 간략하게 정리해 보았습니다.

Go 제네릭

타입 파라미터

['식별자' '타입 제한자', '식별자' '타입 제한자']

Go 제네릭 함수

package main

import "fmt"

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}
func minInt16(a, b int16) int16 {
	if a < b {
		return a
	}
	return b
}
func main() {
	var (
		a int = 10
		b int = 20
	)
	fmt.Println(min(a, b))
	var (
		c int16 = 10
		d int16 = 20
	)
	fmt.Println(minInt16(c, d))
}
package main

import "fmt"

func print[T any](a T) { // func name['식별자' '타입 제한자']
	fmt.Println(a)
}

func main() {
	var (
		a int     = 10
		b float32 = 3.14
		c string  = "hello"
	)
	print(a)
	print(b)
	print(c)
}

any와 빈 인터페이스와의 차이

package main

import "fmt"

func Print(s interface{}) {
	fmt.Print(s)
}

func main() {
	Print("hello")
}
package main

import "fmt"

func foo1(a interface{}) interface{} {
	return a
}

func foo2[T any](a T) T {
	return a
}

func main() {
	var (
		a int = 10
		b int = 20
		c int
	)
	c = foo1(a).(int)   // 리턴 타입이 interface{} 이다.
	fmt.Println(c)
	c = foo2(b) // 리턴 타입이 int이다.
	fmt.Println(c)
}

타입 제한자

package main

import "fmt"

func min[T any](a, b T) T {
	if a < b { // 문법 오류가 발생. any는 < 연산을 지원하지 않는다.
		return a
	}
	return b
}

func main() {
	var (
		a int = 10
		b b   = 20
	)
	fmt.Println(min(a, b))
}
package main

import "fmt"

func min[T int | int16 | int32 | int64 | float32 | float64](a, b T) T {
	if a < b { // 위 타입들이 < 연산자를 지원하기 때문에 문법 오류가 없다.
		return a
	}
	return b
}

func main() {
	var (
		a int     = 10
		b int     = 20
		c int16   = 10
		d int16   = 20
		e float32 = 3.14
		f float32 = 1.14
	)
	fmt.Println(min(a, b))
	fmt.Println(min(c, d))
	fmt.Println(min(e, f))
}

타입 제한자 선언

package main

import "fmt"

type ComparableNumbers interface {
	int | int16 | int32 | int64 | float32 | float64
}

func min[T ComparableNumbers](a, b T) T {
	if a < b { // 위 타입들이 < 연산자를 지원하기 때문에 문법 오류가 없다.
		return a
	}
	return b
}

func main() {
	var (
		a int     = 10
		b int     = 20
		c int16   = 10
		d int16   = 20
		e float32 = 3.14
		f float32 = 1.14
	)
	fmt.Println(min(a, b))
	fmt.Println(min(c, d))
	fmt.Println(min(e, f))
}
package main

import "fmt"

type Integer interface {
	int | int16 | int32 | int64
}

type Float interface {
	float32 | float64
}

type ComparableNumbers interface {
	Integer | Float
}

func min[T ComparableNumbers](a, b T) T {
	if a < b {
		return a
	}
	return b
}

func main() {
	var (
		a int     = 10
		b int     = 20
		c int16   = 10
		d int16   = 20
		e float32 = 3.14
		f float32 = 1.14
	)
	fmt.Println(min(a, b))
	fmt.Println(min(c, d))
	fmt.Println(min(e, f))
}

타입 제한자와 인터페이스

package main

import (
	"fmt"
)

// 인터페이스. 타입 제한자로 사용 가능
type ToString interface {
	String() string
}

func PrintCat[T ToString](a, b T) {
	fmt.Printf("%s-%s", a.String(), b.String())
}

// 타입 제한자. 인터페이스로 사용 불가능
type Integer interface {
	~int8 | ~int16 | ~int32 | ~int64 | ~int
}

// 문법 오류 발생
func PrintMin1(a, b Integer) {
	if a < b {
		fmt.Println(a.String())
	} else {
		fmt.Println(b.String())
	}
}

// 타입 제한자+인터페이스 ==> 타입 제한자. 인터페이스로 사용 불가능
type Stringer interface {
	Integer
	ToString
}

func PrintMin2[T Stringer](a, b T) {
	if a < b {
		fmt.Println(a.String())
	} else {
		fmt.Println(b.String())
	}
}

// 문법 오류 발생
func PrintMin3(a, b Stringer) {
	if a < b {
		fmt.Println(a.String())
	} else {
		fmt.Println(b.String())
	}
}

type MyInt int

func (m MyInt) String() string {
	return fmt.Sprintf("%d", m)
}

func main() {
	var a MyInt = 10
	var b MyInt = 100
	PrintMin(a, b)
	PrintCat(a, b)
}

constraints 패키지

type Ordered

package main

import (
	"fmt"
	"golang.org/x/exp/constraints"
)

func min[T constraints.Ordered](a, b T) T {
	if a < b {
		return a
	}
	return b
}

func main() {
	var (
		a int     = 10
		b int     = 20
		c int16   = 10
		d int16   = 20
		e float32 = 3.14
		f float32 = 1.14
	)
	fmt.Println(min(a, b))
	fmt.Println(min(c, d))
	fmt.Println(min(e, f))
	var (
		h = "Hello"
		i = "World"
	)
	fmt.Println(min(h, i))
}
type Float interface {
	~float32 | ~float64
}
package main

import (
	"fmt"
)

type Integer interface {
	int | int8 | int16 | int32 | int64
}

type MyInt int

func min[T Integer](a, b T) T {
	if a < b {
		return a
	}
	return b
}

func main() {
	var (
		a int = 10
		b int = 20
	)
	fmt.Println(min(a, b))
	var (
		c MyInt = 10
		d MyInt = 20
	)
	fmt.Println(min(c, d))	// possibly missing ~ for int in constraint Integer
	// type Integer 안에 'int' 앞에 틸트(~)를 붙여워야 에러가 안난다. '~int'
}
package main

import (
	"fmt"
)

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

type MyInt int

func min[T Integer](a, b T) T {
	if a < b {
		return a
	}
	return b
}

func main() {
	var (
		a int = 10
		b int = 20
	)
	fmt.Println(min(a, b))
	var (
		c MyInt = 10
		d MyInt = 20
	)
	fmt.Println(min(c, d))
}

제네릭 타입

package main

import "fmt"

type Node[T any] struct {
	val  T  // struct 의 value 타입을 T로 사용한다.
	next *Node[T]
}

func NewNode[T any](v T) *Node[T] { // 새로운 Node를 만들 때도 제네릭이 필요하다.
	return &Node[T]{val: v}
}

/*
Node의 메소드인 Push에 제네릭 T가 포함된다. 하지만 이곳에서 새로운 다른 제네릭을 선언하거나 사용하는 것은 문법 오류이다.
문법오류: func (n *Node[T]) Push[F any](f F) * Node[T] 
*/
func (n *Node[T]) Push(v T) *Node[T] {
	node := NewNode(v)
	n.next = node
	return node
}

func main() {
	node1 := NewNode(1) // *Node[int]
	node1.Push(2).Push(3).Push(4)

	for node1 != nil {
		fmt.Println(node1.val)
		node1 = node1.next
	}

	node2 := NewNode("hello") // *Node[string]
	node2.Push("how").Push("are").Push("you").Push("?")

	for node2 != nil {
		fmt.Println(node2.val)
		node2 = node2.next
	}
}

/*
1
2
3
4
hello
how
are
you
?
*/

제네릭 함수

package main

import (
	"fmt"
	"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(i int) int {
		return i * 2
	})
	fmt.Println(doubled)

	uppered := Map([]string{"Hello", "world", "abc"}, func(s string) string {
		return strings.ToUpper(s)
	})
	fmt.Println(uppered)

	toString := Map([]int{1, 2, 3}, func(i int) string {
		return fmt.Sprintf("str%d", i)
	})
	fmt.Println(toString)
}

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

요약

참조