hirarin blog

目指せ週休3日

【Go Advent Calendar 2020 7日目】 Genericsを試してみた

この記事はGo Advent Calendar 2020 7日目の記事です。

qiita.com

はじめに

  • Goに総称型(Generics)が導入されそうですね
  • ということで、Genericsの使い方のキャッチアップをしてみました

使ってみた所感

  • 配列操作がとにかく楽
  • プロダクトコードで使う時はanyより制約を都度つけていくのがよさそう
  • interface{}やreflectを減らしつつ型安全を守りながらの汎用的な実装、良いですね

使ってみた

Genericsがすでに使える The go2go Playground で動きを確認していきます。

① 配列操作をまとめる

ポインターの構造体の配列から新規のポインタ配列を作成する関数があるとします。 これをGenericsを使って書き換えてみます。(copyだと元とコピー先のポインターは変わらないので今回は使いません)

Article構造体の配列とVideo構造体の配列があるとき、Genericsがない場合は以下のようにそれぞれの型に合った関数を作成する必要があります。

https://go2goplay.golang.org/p/pEywIBsE_YK

// Article用
func NewArticlesPointerSlice(as []*Article) []*Article {
    res := make([]*Article, 0, len(as))
    for i := range as {
        tmp := *as[i]
        res = append(res, &tmp)
    }
    return res
}

// Video用
func NewVideosPointerSlice(vs []*Video) []*Video {
    res := make([]*Video, 0, len(vs))
    for i := range vs {
        tmp := *vs[i]
        res = append(res, &tmp)
    }
    return res
}

 

これをanyを使って書き換えてみます。

https://go2goplay.golang.org/p/3Qazp6UrpQL

// Genericsを使う
func NewPointerSlice[T any](cs []*T) []*T {
    res := make([]*T, 0, len(cs))
    for i := range cs {
        tmp := *cs[i]
        res = append(res, &tmp)
    }
    return res
}

二つの関数を一つにまとめることができました。

[T any]のanyは制約のないどんな型でも許容する状態なので、次に制約をつけて型を絞ってみます。

制約はinterfaceを使って宣言します。

https://go2goplay.golang.org/p/yPCjW-rxD4k

type Duplicatable interface {
    type Article, Video
}

// Genericsを使う
func NewPointerSlice[T Duplicatable](cs []*T) []*T {
    res := make([]*T, 0, len(cs))
    for i := range cs {
        tmp := *cs[i]
        res = append(res, &tmp)
    }
    return res
}

[T any]から[T Duplicatable]に制約を変更しました。 DuplicatableはArticleとVideoを許可する制約です。

試しにVideoの制約を外して実行すると、以下のようなエラーがでました。

type Duplicatable interface {
    type Article
}

// type checking failed for main
// prog.go2:46:35: Video does not satisfy Duplicatable (Video or struct{ID int; Title string; URL string} not found in Article)

 他の型で使いたい時はDuplicatableに型を追加することでNewPointerSlice関数を使えるようになります。

② 振る舞いを指定するinterfaceを使った配列を使う

制約に今までの振る舞い(メソッド)を指定するinterfaceも使えました。 (ただしDuplicatableのようなtypeリストと一緒に定義することはできないそうです)

https://go2goplay.golang.org/p/HjVPJMAb2xn

func (a Article) GetKey() string { return fmt.Sprintf("article:%d:%s", a.ID, a.Title) }
func (v Video) GetKey() string { return fmt.Sprintf("video:%d:%s", v.ID, v.Title) }

type Content interface {
    GetKey() string
}

func Print[T Content](cs []*T) {
    for _, c := range cs {
        fmt.Println("Key is", c.GetKey())
    }
}

このあたりのinterfaceの使い方は今までと同じですが、配列に対して使えるのはやはり便利ですね。

ちなみにinterfaceを満たしていないと、以下のようなエラーがでました。

func (a Article) GetKey() string { return fmt.Sprintf("article:%d:%s", a.ID, a.Title) }
// func (v Video) GetKey() string { return fmt.Sprintf("video:%d:%s", v.ID, v.Title) }

type Content interface {
    GetKey() string
}

func Print[T Content](cs []*T) {
    for _, c := range cs {
        fmt.Println("Key is", c.GetKey())
    }
}

// type checking failed for main
// prog.go2:43:2: Video does not satisfy Content (missing method GetKey)

おわりと参考リンク

Genericsのさわりの部分を試してみました。 本格導入されるまでに、Genericsでできる他のことやどのような場面で活用できそうかを考えてみようと思います。