Upgrade to Pro — share decks privately, control downloads, hide ads and more …

静的解析と学ぶ型パラメタ(ジェネリクス) - mercari.go#18

静的解析と学ぶ型パラメタ(ジェネリクス) - mercari.go#18

この資料はmercari.go#18にて発表を行った際に用いた資料です。

イベントページ:https://mercari.connpass.com/event/235116/
ハッシュタグ:#mercarigo
動画:https://tenn.in/typeparamanalysis-video
資料(Googleスライド):https://tenn.in/typeparamanalysis

■ 登壇者&主催者

・名前:tenntenn / 上田拓也
・HP:https://tenntenn.dev
・Twitter:https://twitter.com/tenntenn

メルカリ/メルペイ所属。バックエンドエンジニアとして日々Goを書いている。Google Developer Expert (Go)。一般社団法人Gophers Japan代表。Go Conference主催者。大学時代にGoに出会い、それ以来のめり込む。人類をGopherにしたいと考え、Goの普及に取り組んでいる。複数社でGoに関する技術アドバイザーをしている。マスコットのGopherの絵を描くのも好き。

■ Gopher道場 自習室

https://gopherdojo.org/studyroom/

Gopher道場とは、実践的なGoを体系的に学べる場です。
Gopher道場 自習室では、以下のようなコンテンツや学びの場を提供します。

・Gopher道場の講義を録画した動画(10時間以上分)
・Slackにおける受講者同士のコミュニティ
・Gopher道場卒業生による課題のレビュー(ボランティアでご協力頂いているのでベストエフォートです)

■ Meety(カジュアル面談)

・ソフトウェアエンジニアの地方移住ってどうなの?:https://meety.net/matches/jyZgDkEEwmMk
・メルカリグループにおけるGoの使いどころ:https://meety.net/matches/LbeVbIACxLqk
・地方からの技術コミュニティへの貢献:https://meety.net/matches/gVeMtImLkWJE

■ お仕事の依頼について

副業にて技術顧問やアドバイザーなどを行っています。過去の実績や問い合わせフォームは以下のURLからご確認ください。
https://tenntenn.dev/ja/job/

#golang #tenntenn #Go言語

tenntenn - Takuya Ueda

January 20, 2022
Tweet

More Decks by tenntenn - Takuya Ueda

Other Decks in Programming

Transcript

  1. The Go gopher was designed by Renée French.
    The gopher stickers was made by Takuya Ueda.
    Licensed under the Creative Commons 3.0 Attributions license.
    静的解析と学ぶ
    型パラメタ(ジェネリクス)
    mercari.go #18
    2022/01/20(木)
    資料URL:https://tenn.in/typeparamanalysis

    View full-size slide

  2. 上田拓也
    Go ビギナーズ

    Go Conference

    @tenntenn
    tenntenn.dev
    Google Developer Expert (Go)
    一般社団法人 Gophers Japan 代表理事
    tenntenn Conference主催
    Experts Team

    View full-size slide

  3. オンラインでセッションを見るときは
    ■ 登壇者(私)を孤独にさせないように!
    ● YouTubeのコメントやTwitterなどのリアクションは大きめに!
    ● 知らなかったことには「へぇ」
    ● 知ってることには「そうそう、それ」
    ● あなたの相槌で登壇者を孤独から救えます

    View full-size slide

  4. 書いたところ
    ■ はじめに
    ● はじめにを書くと感慨深いです
    ■ 0.2 入出力
    ● ioパッケージを中心にio/fsパッケージにも触れてます
    ■ 2.1 Nature Remoによる家電の操作
    ● APIクライアントについて書いてます
    ■ 4.1 高度なテキスト変換
    ● x/text/tranformパッケージについて書いてます
    参考:技術専門誌編集者とメルペイエンジニアが
    2年間続けた連載「作品で魅せる
    Goプログラミング」のすべて

    View full-size slide

  5. Go1.18 リリースパーティ
    ■ 型パラメタの詳細はこちらのイベントで!
    ● 2022年02月18日(金) 19:00 〜
    ● https://gocon.connpass.com/event/234198/
    ● Remoを使って開催予定!
    ○ YouTube Liveでの配信はありません
    ○ アーカイブは残す予定です

    View full-size slide

  6. こちらもどうぞ
    ■ Go1.18最新情報 - tenntenn Conference 2022
    ● 資料:https://tenn.in/go118
    ● 動画:https://youtu.be/5XRXubGV3tI?t=17178

    View full-size slide

  7. 静的解析についてはこちら
    ■ 静的解析とコード生成
    ● http://tenn.in/analysis
    ● プログラミング言語Go完全入門の第14章
    ● 300+ページの超大作

    View full-size slide

  8. インターンシップで学ぶ
    ■ Go1.18が学べる短期インターシップ(5日間)
    ● https://mercan.mercari.com/articles/31914/
    ● 1/20 19時まで応募可
    ● 学生向け
    ● 時給:2,500円

    View full-size slide

  9. 今日話すこと
    ■ 型パラメタ(ジェネリクス)を含むコードの静的解析
    ● 型パラメタの概要
    ● Goにおける静的解析の概要
    ● 型パラメタを含むコードの抽象構文木
    ● 型パラメタを含むコードの型情報

    View full-size slide

  10. 型パラメタ
    ■ ジェネリックな型や関数を定義できる
    ● 実際の型を使用する側が指定できる
    ● 型制約としてインタフェースが使用できる
    ● 型セットという概念が登場
    var printStr func([]string) = Print[string]
    printStr([]string{"Hello, ", "playground\n"})
    Print[T]
    Print[string]
    func([]string)
    Print[int]
    func([]int)
    T => string
    T => int
    インスタンス化
    func Print[T any](s []T) {
    for _, v := range s {
    fmt.Print(v)
    }
    }

    View full-size slide

  11. 静的解析
    ■ 静的解析とは?
    ● プログラムを実行せずに解析すること
    ● ソースコードの構造や意味を解析する
    ● 例:lint、コード補完、 コードフォーマッタ
    11
    コーディング
    010100
    101000
    101000
    コンパイル
    010100
    101000
    101000
    デプロイ リリース
    テスト QA 監視
    静的解析

    View full-size slide

  12. Goにおける静的解析のフェーズ
    ■ 静的解析はいくつかのフェーズに分かれている
    ● 後のフェーズにいくこと、さらに詳しい情報が手に入る
    ● 各フェーズで手に入る情報を使い分けながら解析する
    ● 各フェーズで手に入る情報の紐付けの仕方がキモ
    ○ このノードの型情報は?など
    12
    構文解析
    型チェック
    静的単一代入形式
    ポインタ解析

    View full-size slide

  13. 静的解析に使えるパッケージ
    ast 抽象構文木 (AST)を提供
    build パッケージに関する情報を集める
    constant 定数に関する型を提供
    doc ドキュメントを ASTから取り出す
    format コードフォーマッタ機能を提供
    importer コンパイラに適した Importerを提供
    parser 構文解析 機能を提供
    printer AST 表示機能を提供
    scanner 字句解析 機能を提供
    token トークンに関する型を提供
    types 型チェックに関する機能を提供
    13
    analysis 静的解析ツールをモジュール化するパッケージ
    ast AST関連のユーティリティ
    callgraph call graph関連
    cfg control flow graph関連
    expect 構造化されたコメントを処理する
    packages Go Modulesを前提としたパッケージ情報の収集から構文解析、
    型チェックまでを行うパッケージ
    pointer ポインタ解析
    ssa Static Single Assignment (SSA) 関連
    types 型情報関連
    ■ 標準で用意されている(goパッケージ)
    ● 準標準的なものもある(golang.org/x/tools/goパッケージ)
    goパッケージのサブパッケージ golang.org/x/tools/goパッケージのサブパッケージ

    View full-size slide

  14. ■ 入力された文字列をトークンとして分解
    字句解析- go/scanner,go/token
    IDENT ADD INT
    トークン
    ソースコード: v + 1
    14

    View full-size slide

  15. 構文解析 - go/parser,go/ast
    ■ トークンを抽象構文木(AST)に変換
    ● AST: Abstract Syntax Tree
    v + 1
    IDENT ADD INT
    ソースコード:
    +
    v 1
    BinaryExpr
    Ident BasicLit
    トークン:
    抽象構文木(AST):
    15

    View full-size slide

  16. 型チェック - go/types,go/constant
    ■ 型情報を抽象構文木から抽出
    ● 識別子の解決
    ● 型の推論
    ● 定数の評価
    n := 100 + 200
    m := n + 300
    定数の評価
    = 300
    型の推論
    -> int
    識別子の解決
    16

    View full-size slide

  17. 静的解析ツールを簡単に作る
    ■ golang.org/x/tools/go/analysisパッケージ
    ● 静的解析ツールのモジュール化を提供するパッケージ
    ● Go1.12からgo vetでも使われるようになった
    ● 構文解析と型チェックは自動で行う
    ● Analyzer単位で開発する
    var Analyzer = &analysis.Analyzer{
    Name: "simple",
    Doc: "simple is simple Analyzer",
    Run: run,
    Requires: []*analysis.Analyzer{inspect.Analyzer},
    }
    func run(pass *analysis.Pass) (interface{}, error) {
    /* 解析処理 */
    return nil, nil
    }
    依存するAnalyzer
    抽象構文木や型情報を保持

    View full-size slide

  18. skeleton
    ■ go/analysis用のスケルトンコードジェネレータ
    ● https://github.com/gostaticanalysis/skeleton
    ● 簡単に静的解析ツールを始めることができる
    ● Analyzer、テストコード、main.goの雛形作ってくれる
    $ skeleton myanalyzer
    myanalyzer
    ├── cmd
    │ └── myanalyzer
    │ └── main.go
    ├── myanalyzer.go
    ├── myanalyzer_test.go
    └── testdata
    └── src
    └── a
    └── a.go

    View full-size slide

  19. goパッケージの何が変わるのか
    ■ GOROOT/api/go1.18.txtを見てみよう
    ● https://cs.opensource.google/go/go/+/master:api/go1.18.txt
    ● 新しく加わる機能が分かる
    $ grep "go/" `go1.18beta1 env GOROOT`/api/go1.18.txt
    pkg go/ast, method (*IndexListExpr) End() token.Pos
    pkg go/ast, method (*IndexListExpr) Pos() token.Pos
    pkg go/ast, type FuncType struct, TypeParams *FieldList
    pkg go/ast, type IndexListExpr struct
    pkg go/ast, type IndexListExpr struct, Indices []Expr
    pkg go/ast, type IndexListExpr struct, Lbrack token.Pos
    pkg go/ast, type IndexListExpr struct, Rbrack token.Pos
    pkg go/ast, type IndexListExpr struct, X Expr
    pkg go/ast, type TypeSpec struct, TypeParams *FieldList
    pkg go/constant, method (Kind) String() string
    (略)

    View full-size slide

  20. 抽象構文木をダンプして確かめる
    ■ ast.Print関数を使う
    ● 第2引数のノードをダンプする関数
    ■ knsh14/astreeを使う(使いたい)
    ● treeコマンドっぽく出すツール
    ● まだ未対応だけどPRは出てるっぽい
    ○ マージお願いします
    ○ https://github.com/knsh14/astree/pull/9
    ast.Print(pass.Fset, pass.Files[0])

    View full-size slide

  21. 抽象構文木から型パラメタを得る
    ■ 関数(ast.FuncType構造体)
    ● 関数の型を表すノード
    ● *ast.FieldList型のTypeParamsフィールドから取得できる
    ● ast.Field構造体で同じ制約の型パラメタのリストを表す
    ○ [X, Y any, Z fmt.Stringer]だとX, Y anyとZ fmt.Stringerの単位
    ○ Namesフィールドが型パラメタのスライス(
    []*ast.Ident型)
    ○ Typeフィールドが型制約(ast.Expr型)
    ■ 型(ast.TypeSpec構造体)
    ● 型宣言のtypeキーワードより後の部分を表すノード
    ● *ast.FieldList型のTypeParamsフィールドから取得できる
    func Print[T any](s []T) {...}
    type Vector[T any] []T

    View full-size slide

  22. ノードから型情報の型パラメタを得る
    ■ 型パラメタの型情報をtypes.TypeParam構造体で表す
    ● types.Typeインタフェースを実装
    ● Constraintメソッドで制約が取得できる
    ■ 型パラメタを表す識別子から取得できる
    ● 例:ast.FuncType構造体のTypeParamsフィールド
    ○ ast.Field構造体のNamesフィールドの要素から取得できる
    ● (*types.Info).TypeOfメソッドを使う

    View full-size slide

  23. 型情報から型パラメタを取得
    ■ 関数(types.Signature型)
    ● TypeParamsメソッドから取得できる
    ● レシーバの型パラメタもRecvTypeParamsメソッドから取得可
    ■ 型(types.Named型)
    ● TypeParamsメソッドから型パラメタを取得
    ● TypeArgsメソッドから型引数が取得できる
    ■ 型パラメタを表すオブジェクト
    ● types.TypeName構造体で表される

    View full-size slide

  24. 制約を取得する
    ■ (*types.TypeParam).Constraintメソッドで取得
    ● *types.Interface型で表す
    ● IsComparebleメソッドで比較かのうかどうか取得
    ○ 組み込みのcompareble制約(インタフェース)
    ○ 型セットがcompareble制約のものの部分集合の制約
    ● IsImplicitメソッドで暗黙のインタフェースか取得
    ○ func f[T ~int]()のようにinterface{ ~int }を省略できる
    ● IsMethodSetメソッドでメソッドセットのみか判定
    ○ 制約以外にも使えるかどうか分かる

    View full-size slide

  25. 型パラメタを持つ関数の呼び出し
    ■ 関数呼び出しはast.CallExpr構造体が表す
    ● Funフィールドに呼び出す関数を表すノードを保持
    ● 型引数が指定してあるかどうかで型が変わる
    ● 型引数が1つの場合は*ast.IndexExpr型の値が入る
    ○ ast.IdexExpr構造体はスライスやマップでも使われる既存の型
    ○ 後方互換性のためにast.IdentListExpr型に統合されなかった
    ● 型引数が2つ以上の場合は*ast.IndexListExpr型の値が入る
    ○ f[string, int]("hoge", 100)のような場合
    ● 型引数なしで型推論をする場合は*ast.Ident型の値が入る
    ○ 関数呼び出しは型引数が省略できる
    ○ 抽象構文木だけでは実際の型引数が分からない

    View full-size slide

  26. 型パラメタのインスタンス化
    ■ types.Instantiate関数を用いる
    ● types.Context構造体はインスタンス化された型情報などを持つ
    ○ 型チェック時のtypes.Config構造体で指定できる
    ● 第2引数の型は型パラメタを持つ型
    ● 第3引数は型引数のスライス
    ● 第4引数はインスタンス化の検証を行うかどうか
    Print[T]
    Print[string]
    func([]string)
    Print[int]
    func([]int)
    T => string
    T => int
    インスタンス化
    func Instantiate(ctxt *Context, orig Type, targs []Type, validate bool) (Type, error)

    View full-size slide

  27. 新しく導入されるトークン
    ■ ~(チルダ):token.TILDE定数
    ● インタフェース要素に使える
    ● interface{ ~string | int }のように記述できる
    InterfaceType = "interface" "{" { InterfaceElem ";" } "}" .
    InterfaceElem = MethodElem | TypeElem .
    MethodElem = MethodName Signature .
    MethodName = identifier .
    TypeElem = TypeTerm { "|" TypeTerm } .
    TypeTerm = Type | UnderlyingType .
    UnderlyingType = "~" Type .

    View full-size slide

  28. 制約のインタフェースを表すノード
    ■ ast.InterfaceType構造体で表す
    ● *ast.FieldList型のMethodsフィールドがインタフェース要素
    ○ Methodsなのは後方互換のため
    ● ~stringなどはast.UnaryExpr型(単項演算式)で表す
    ● int | ~stringなどはast.BinaryExpr型(2項演算式)で表す
    InterfaceType = "interface" "{" { InterfaceElem ";" } "}" .
    InterfaceElem = MethodElem | TypeElem .
    MethodElem = MethodName Signature .
    MethodName = identifier .
    TypeElem = TypeTerm { "|" TypeTerm } .
    TypeTerm = Type | UnderlyingType .
    UnderlyingType = "~" Type .

    View full-size slide

  29. デモ
    参考:https://github.com/gostaticanalysis/examples/tree/main/typeparam

    View full-size slide

  30. まとめ
    ■ 型パラメタを含むコードも静的解析できる
    ● 標準パッケージで静的解析ができるから言語仕様に追従してくれる
    ● 既存の静的解析のエコシステムを壊さずに導入されている
    ● これから増えてくるジェネリクスのコードに対応しよう
    ○ さっそく、go vetのprintf.Analyzerは対応している

    View full-size slide