$30 off During Our Annual Pro Sale. View Details »

introduction to type sets

nobishii
August 20, 2021

introduction to type sets

Go言語のジェネリクス(型パラメタ)導入後の言語仕様には、型セット(type sets)というものが導入されます。このスライドではその内容と必要性を説明します。

nobishii

August 20, 2021
Tweet

More Decks by nobishii

Other Decks in Programming

Transcript

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

    View Slide

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

    View Slide

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

    View Slide

  4. 参考資料(公式)
    ● 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言語仕様書に型パラメータの仕様を反映するためのパッチです。

    View Slide

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

    View Slide

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

    View Slide

  7. 例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

    View Slide

  8. 例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]
    }

    View Slide

  9. 例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が使える

    View Slide

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

    View Slide

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

    View Slide

  12. クイズ(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のメソッドセットを包含すること」

    View Slide

  13. メソッドセットとは
    ● 全ての型はメソッドセットを持つ(空集合の場合もある)
    ○ メソッドセットの「セット」は数学の教科書に載っている「集合」のこと
    ○ メソッドセットはメソッドの集合
    ● インタフェース型の例
    ○ 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, .... }

    View Slide

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

    View Slide

  15. 例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]
    }

    View Slide

  16. 例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を実装する型は全て比較演算子 > を
    サポートするのでここで > を使える

    View Slide

  17. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    の両方を満たす型からなる集合

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  30. 型セットのコードをローカル環境で動かす
    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 さん

    View Slide

  31. 練習問題解答例
    // 解答例
    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()のメソッド宣言がはいる(省略)

    View Slide