Slide 1

Slide 1 text

初めての型セット Go1.17 Release Party 2021/08/20 @shino_nobishii

Slide 2

Slide 2 text

自己紹介 ● Nobishii (@shino_nobishii) ● 1年半くらい仕事でGo言語を使っています ● Go言語仕様書ファン ○ #gospecreading

Slide 3

Slide 3 text

この発表について ● ジェネリクス・型パラメータに関係する発表です ● 型パラメータの実用よりも、次の「考え方」の理解を目指します ○ ある型があるインタフェースを実装 (implement)するとはどういうことか ○ メソッドセットとは何か ○ 型セットとは何か・どう決まるか ○ 型セットとメソッドセットはどういう関係にあるか ● 実用例は次の@syumai さんの発表をお楽しみに!

Slide 4

Slide 4 text

参考資料(公式) ● Type Parameters Proposal ○ 型パラメータのまとまったドキュメントです。 ○ 型セットの部分がtype listになっているところだけは古い内容なので注意してください。 ○ 型推論アルゴリズムや豊富な具体例など現在ここにしか書かれていない内容は多いです。 ● spec: generics: use type sets to remove type keyword in constraints · Issue #45346 · golang/go ○ type listの代わりに型セットを使うプロポーザル。ステータスは Acceptedです。 ● The Go Programming Language Specification ○ 現在のGo言語仕様書です。 ● Go言語仕様書のレビュー中のCL ○ Go言語仕様書に型パラメータの仕様を反映するためのパッチです。

Slide 5

Slide 5 text

参考資料(公式以外) ● Goにこれから入る機能について @tenntenn さん ○ 型推論などこの発表で触れられない事柄含めて広くカバーされています ● 入門Go言語仕様/Underlying Type @DQNEO さん ○ underlying typeについてわかりやすく丁寧に書かれています ● 発表者 (@shino_nobishii)の記事 ○ Go の "Type Sets" proposal を読む ■ type listから型セットに変更されたモチベーションを書いています ○ Type Sets Proposalを読む(2) ■ 「実装(implement)する」の判定アルゴリズムと言語仕様の関係を書いています

Slide 6

Slide 6 text

Goのジェネリクスの要点 ※リリースはGo1.18以後。細部はまだ変わるかもしれません ● 「関数」と「型」は、「型パラメータ」を持つことができる ● 型パラメータが満たすべき型制約は、インタフェースによって表現する ● ある型TがインタフェースIFで表される型制約を満たすのは、型TがインタフェースIF を実装(implement)するときである 参考: Type Parameters Proposal

Slide 7

Slide 7 text

例1: Map(型パラメタを持つ関数) func Map[T1, T2 any](s []T1, f func(T1) T2) []T2 { r := make([]T2, len(s)) for i, v := range s { r[i] = f(v) } return r } func main() { s := Map([]int{1,2,3}, func(x int) string { return fmt.Sprintf("String: %d", x) }) fmt.Println(s) // [String: 1 String: 2 String: 3] } // 以上 Type Parameters Proposalから引用 https://go2goplay.golang.org/p/WWXh6ThtGFY

Slide 8

Slide 8 text

例2: sortとSortable(型パラメータを持つ型) type Item int func(i Item) CompareTo(j Item) int { return int(j-i) } // 降順ソート func main() { xs := &Sortable[Item]{1,3,2,4,5,8,7} sort.Sort(xs) fmt.Println(xs) // &[8 7 5 4 3 2 1] }

Slide 9

Slide 9 text

例2: sort type Comparable[T any] interface { CompareTo(T) int } type Sortable[T Comparable[T]] []T // この型にsort.Interfaceを実装させる func (s *Sortable[T]) Less(i, j int) bool { return (*s)[i].CompareTo((*s)[j]) < 0 } // LenとSwapメソッドも必要ですが省略します https://go2goplay.golang.org/p/1SWrvbqCuv8 型パラメタ付きの interface型 interface型を型制約に用いる。 Item型はComparable[Item]を満たす 型制約のおかげで CompareToが使える

Slide 10

Slide 10 text

型制約はインターフェースで表される ● Goのジェネリクスは、型制約をインターフェースで表す ● 「型制約を満たす」は「インタフェースを実装する」と同義 ● Comparable型制約を満たすためには、Comparableインタフェースを実装すれば よい ● 「実装する」の意味が重要になる type Comparable[T any] interface { CompareTo(T) int }

Slide 11

Slide 11 text

クイズ(Go1.16や1.17でお考えください) 「型TがインタフェースIを実装する」とはどういう意味でしょうか?

Slide 12

Slide 12 text

クイズ(Go1.16でお考えください) 「型TがインタフェースIを実装する」とはどういう意味でしょうか? https://golang.org/ref/spec#Interface_types より引用: A variable of interface type can store a value of any type with a method set that is any superset of the interface. Such a type is said to implement the interface. 【解答例】 「型TがインタフェースIFを実装する」とは、 「TのメソッドセットがIFのメソッドセットを包含すること」

Slide 13

Slide 13 text

メソッドセットとは ● 全ての型はメソッドセットを持つ(空集合の場合もある) ○ メソッドセットの「セット」は数学の教科書に載っている「集合」のこと ○ メソッドセットはメソッドの集合 ● インタフェース型の例 ○ io.Readerのメソッドセットは Readメソッドのみからなる集合 { Read } ○ io.ReadCloserのメソッドセットは { Read, Close } ○ io.ReadWriteCloserのメソッドセットは{ Read, Write, Close } ● 非インタフェース型の例 ○ *os.Fileのメソッドセットは { Read, Write, Close, Sync, … }  ○ *bytes.Bufferのメソッドセットは { Read, Write, Bytes, … } ○ *gzip.Readerのメソッドセットは { Read, Close, .... }

Slide 14

Slide 14 text

「型TがインタフェースIを実装する」とは、 「TのメソッドセットがIのメソッドセットを包含すること」

Slide 15

Slide 15 text

例3: sortとSortable再び type Item int // 今度はCompareToを実装しない func main() { xs := &Sortable[Item]{1,3,2,4,5,8,7} sort.Sort(xs) fmt.Println(xs) // &[8 7 5 4 3 2 1] }

Slide 16

Slide 16 text

例3 type Item int // 再掲 type Number interface { ~int | ~float64 } type Sortable[T Number] []T func (s *Sortable[T]) Less(i, j int) bool { return (*s)[i] > (*s)[j] } // LenとSwapは省略しました https://go2goplay.golang.org/p/Q6dMbKH-x-u 新しい文法。underlying type(※後述)が intまたはfloat64である型はNumberを実装する。 Numberを実装する型は全て比較演算子 > を サポートするのでここで > を使える

Slide 17

Slide 17 text

underlying type ● 全ての型にはunderlying typeがある ● type A Bのように型定義されたAのunderlying typeは次のように決まる ● AからBに遡り、Bの型定義があればさらに遡ることを繰り返す ● 最終的には次のような型に辿り着き、それ以上遡れなくなる ○ int ○ []string ○ *float64 ○ struct { ID int; Name string } ● 行き着いた終点にある型がAのunderlying type ● 参考資料 ○ Go言語仕様書 ○ https://speakerdeck.com/dqneo/go-language-underlying-type

Slide 18

Slide 18 text

ちょっと待って。「実装する」ってなんだっけ? ● 「型制約を満たす」は「インタフェースを実装する」と同義 ● 「型TがインタフェースIを実装する」とは、「TのメソッドセットがIのメソッド セットを包含すること」だったはず(Go1.16) ● Numberのメソッドセットって何? ● 「実装(implement)」概念のアップデートが必要 type Number interface { ~int | ~float64 }

Slide 19

Slide 19 text

型セットによる「実装」の再定義 ● 全ての型は型セットを持つ ○ 型だけではなく、インタフェース要素(後述)も型セットを持つ ○ 型セットの「セット」も集合のこと 【新しい「実装」の定義】 「ある型Tがあるインタフェース IFを実装する」とは、 「型TがインタフェースIFの型セットに含まれる」ことを言う 【※従来の「実装」の定義】 「型TがインタフェースIFを実装する」とは、「型 TのメソッドセットがIFの メソッドセットを包含すること」を言う

Slide 20

Slide 20 text

型セットのルール(一部) ● インタフェースではない型の型セットは、その型のみからなる集合 ○ intの型集合は{int} ● メソッド定義のみを持つインタフェース型の型セットは、それらのメソッド全てを実装する型全体からなる集合 ● 実装の「新しい定義」と「従来の定義」で結論が一致することがわかる メソッドセット 型セット

Slide 21

Slide 21 text

メソッドセットと型セットの関係 メソッドセット 型セット

Slide 22

Slide 22 text

ジェネリクス以後のインタフェース型 interface { ~int | string fmt.Stringer Print() } メソッド定義 (MethodSpec) 型表式(TypeExpr) 項(TypeTerm)を複数持つ。 このようなものをunionsと言う 型表式(TypeExpr) 項(TypeTerm)を1つだけ持つ インタフェース型は0個以上のイン タフェース要素(InterfaceElem)か らなる。 TypeExprもMethodSpecもインタ フェース要素の一種。

Slide 23

Slide 23 text

型セットのルール(一般の場合) ● インタフェース型以外の型の型セットは、その型のみからなる集合 ● 空インタフェース interface{}の型セットは、全ての型からなる集合 ● 空インタフェース以外のインタフェースの型セットは、そのインタフェース要素の型 セットの共通部分 ● インタフェース要素の型セットは次のように決まる ○ メソッド定義の型セットは、そのメソッドをメソッドセットに含む全ての型からなる集合 ○ ~Tの型セットは、underlying typeがTである全ての型からなる集合 ○ T1 | T2 | … Tn というunionの型セットは、T1...Tnの型セットの合併集合

Slide 24

Slide 24 text

ルールを当てはめてみる interface { ~int | string fmt.Stringer Print() } // 練習問題: これを実装する型を定義してみましょう Print() を実装する型か らなる集合 ~intとstringの合併集合 String() string を実装する型 からなる集合 これら3つの共通部分: - String() stringとPrint()を両 方実装し、かつ、 - string型であるか、 underlying typeがintであ る の両方を満たす型からなる集合

Slide 25

Slide 25 text

まとめ(1) なぜ型セットの概念が必要か ● 型パラメータProposalは型制約をインタフェースで表す ● 「型制約を満たす」は「インタフェースを実装する」と同じ意味としたい ● <や==による比較可能性などは従来のインタフェースで表せない ● そこで「インタフェース型」で表せるものを拡張することにした ● それに合わせて「実装(implement)する」の概念も拡張する必要が生じた ● 新しい「実装する」を表現することばとして「型セット」の概念が生まれた

Slide 26

Slide 26 text

まとめ(2) 型セットのルールと「実装(implement)」 ● インタフェース型以外の型の型セットは、その型のみからなる集合 ● 空インタフェース interface{}の型セットは、全ての型からなる集合 ● 空インタフェース以外のインタフェースの型セットは、そのインタフェース要素の型 セットの共通部分 ● インタフェース要素の型セットは次のように決まる ○ メソッド定義の型セットは、そのメソッドをメソッドセットに含む全ての型からなる集合 ○ ~Tの型セットは、underlying typeがTである全ての型からなる集合 ○ T1 | T2 | … Tn というunionの型セットは、T1...Tnの型セットの合併集合 「型TがインタフェースIFを実装する」とは、 「型TがインタフェースIFの型セットに含まれる」ことを言う

Slide 27

Slide 27 text

補足資料集 ● 発表は以上です ● 以下は補足資料です

Slide 28

Slide 28 text

メソッドセットの定義も更新される ● インタフェース型のメソッドセットは、その型セットに属する型のメソッドセットの共通 部分である ● 型パラメータのメソッドセットはその型制約のメソッドセットに等しい ● それ以外の型のメソッドセットは従来通り

Slide 29

Slide 29 text

※数式の方がわかりやすい人向け

Slide 30

Slide 30 text

型セットのコードをローカル環境で動かす go install golang.org/dl/gotip@latest gotip download dev.typeparams gotip run -gcflags="-G=3" main.go # go1.17 run -gcflags="-G=3" main.go でも型セット部分なしの型 パラメータなら動きます 参考: Type Sets Proposal 勉強会用メモ by @syumai さん

Slide 31

Slide 31 text

練習問題解答例 // 解答例 type MyInt int func(x MyInt) String() string { return fmt.Sprintf(“%d”, x) } func(x MyInt) Print() { fmt.Println(x.String()) } // なお、次のMyStringは不正解です。 // MyStringはstringの型セットの要素ではないからです。 type MyString string // ここにString() stringとPrint()のメソッド宣言がはいる(省略)