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
  2. 上田拓也 Go ビギナーズ 
 Go Conference 
 @tenntenn tenntenn.dev Google

    Developer Expert (Go) 一般社団法人 Gophers Japan 代表理事 tenntenn Conference主催 Experts Team
  3. 書いたところ ▪ はじめに • はじめにを書くと感慨深いです ▪ 0.2 入出力 • ioパッケージを中心にio/fsパッケージにも触れてます

    ▪ 2.1 Nature Remoによる家電の操作 • APIクライアントについて書いてます ▪ 4.1 高度なテキスト変換 • x/text/tranformパッケージについて書いてます 参考:技術専門誌編集者とメルペイエンジニアが 2年間続けた連載「作品で魅せる Goプログラミング」のすべて
  4. Go1.18 リリースパーティ ▪ 型パラメタの詳細はこちらのイベントで! • 2022年02月18日(金) 19:00 〜 • https://gocon.connpass.com/event/234198/

    • Remoを使って開催予定! ◦ YouTube Liveでの配信はありません ◦ アーカイブは残す予定です
  5. 型パラメタ ▪ ジェネリックな型や関数を定義できる • 実際の型を使用する側が指定できる • 型制約としてインタフェースが使用できる • 型セットという概念が登場 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) } }
  6. 静的解析に使えるパッケージ 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パッケージのサブパッケージ
  7. 構文解析 - go/parser,go/ast ▪ トークンを抽象構文木(AST)に変換 • AST: Abstract Syntax Tree

    v + 1 IDENT ADD INT ソースコード: + v 1 BinaryExpr Ident BasicLit トークン: 抽象構文木(AST): 15
  8. 型チェック - go/types,go/constant ▪ 型情報を抽象構文木から抽出 • 識別子の解決 • 型の推論 •

    定数の評価 n := 100 + 200 m := n + 300 定数の評価 = 300 型の推論 -> int 識別子の解決 16
  9. 静的解析ツールを簡単に作る ▪ 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 抽象構文木や型情報を保持
  10. 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
  11. 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 (略)
  12. 抽象構文木をダンプして確かめる ▪ ast.Print関数を使う • 第2引数のノードをダンプする関数 ▪ knsh14/astreeを使う(使いたい) • treeコマンドっぽく出すツール •

    まだ未対応だけどPRは出てるっぽい ◦ マージお願いします ◦ https://github.com/knsh14/astree/pull/9 ast.Print(pass.Fset, pass.Files[0])
  13. 抽象構文木から型パラメタを得る ▪ 関数(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
  14. 型情報から型パラメタを取得 ▪ 関数(types.Signature型) • TypeParamsメソッドから取得できる • レシーバの型パラメタもRecvTypeParamsメソッドから取得可 ▪ 型(types.Named型) •

    TypeParamsメソッドから型パラメタを取得 • TypeArgsメソッドから型引数が取得できる ▪ 型パラメタを表すオブジェクト • types.TypeName構造体で表される
  15. 制約を取得する ▪ (*types.TypeParam).Constraintメソッドで取得 • *types.Interface型で表す • IsComparebleメソッドで比較かのうかどうか取得 ◦ 組み込みのcompareble制約(インタフェース) ◦

    型セットがcompareble制約のものの部分集合の制約 • IsImplicitメソッドで暗黙のインタフェースか取得 ◦ func f[T ~int]()のようにinterface{ ~int }を省略できる • IsMethodSetメソッドでメソッドセットのみか判定 ◦ 制約以外にも使えるかどうか分かる
  16. 型パラメタを持つ関数の呼び出し ▪ 関数呼び出しはast.CallExpr構造体が表す • Funフィールドに呼び出す関数を表すノードを保持 • 型引数が指定してあるかどうかで型が変わる • 型引数が1つの場合は*ast.IndexExpr型の値が入る ◦

    ast.IdexExpr構造体はスライスやマップでも使われる既存の型 ◦ 後方互換性のためにast.IdentListExpr型に統合されなかった • 型引数が2つ以上の場合は*ast.IndexListExpr型の値が入る ◦ f[string, int]("hoge", 100)のような場合 • 型引数なしで型推論をする場合は*ast.Ident型の値が入る ◦ 関数呼び出しは型引数が省略できる ◦ 抽象構文木だけでは実際の型引数が分からない
  17. 型パラメタのインスタンス化 ▪ 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)
  18. 新しく導入されるトークン ▪ ~(チルダ):token.TILDE定数 • インタフェース要素に使える • interface{ ~string | int

    }のように記述できる InterfaceType = "interface" "{" { InterfaceElem ";" } "}" . InterfaceElem = MethodElem | TypeElem . MethodElem = MethodName Signature . MethodName = identifier . TypeElem = TypeTerm { "|" TypeTerm } . TypeTerm = Type | UnderlyingType . UnderlyingType = "~" Type .
  19. 制約のインタフェースを表すノード ▪ 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 .