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

Go に Generics がやってきた

Smith
March 13, 2022

Go に Generics がやってきた

2022/03/13 に催された Drecom SRE Sunday Vol.2 での登壇資料です。

Smith

March 13, 2022
Tweet

More Decks by Smith

Other Decks in Technology

Transcript

  1. • 自己紹介
 • 本資料の対象者
 • Go ってなんだっけ
 • 静的型付けってなんだっけ
 •

    ポリモーフィズムってなんだっけ
 • Generics ってなんだっけ
 • Generics のなかった Go
 • まとめ
 AGENDA

  2. • 自己紹介
 • 本資料の対象者
 • Go ってなんだっけ
 • 静的型付けってなんだっけ
 •

    ポリモーフィズムってなんだっけ
 • Generics ってなんだっけ
 • Generics のなかった Go
 • まとめ
 AGENDA

  3. @dolow 自己紹介
 smith := &Employee[Drecom]{ Divisions: []Division{ Division{“BtoB事業本部”, “AROW部”}, Division{“SRE部”,

    “クライアントグループ”}, }, RecentTechs: []Tech{ techs.GeospacialInformation, techs.Blockchain, techs.GeneralWebApp, }, Books: []Book{ Book{“HTML5 ゲーム開発の教科書”, “ボーンデジタル社”}, } } @do_low
  4. @dolow 自己紹介
 smith := &Employee[Drecom]{ Divisions: []Division{ Division{“BtoB事業本部”, “AROW部”}, Division{“SRE部”,

    “クライアントグループ”}, }, RecentTechs: []Tech{ techs.GeospacialInformation, techs.Blockchain, techs.GeneralWebApp, }, Books: []Book{ Book{“HTML5 ゲーム開発の教科書”, “ボーンデジタル社”}, } } @do_low ??

  5. • 自己紹介
 • 本発表の主な対象者
 • Go ってなんだっけ
 • 静的型付けってなんだっけ
 •

    ポリモーフィズムってなんだっけ
 • Generics ってなんだっけ
 • Generics のなかった Go
 • まとめ
 AGENDA

  6. 本発表の主な対象者
 基本的には初学者を想定した易しめな内容です。
 下記に当てはまる方は、今回の発表内容はちょうど良いと思います。
 • 「どうも、動的型付けの世界から来ました!」
 • 「Go で Hello World

    しました!」
 • 「Generics ってなんですか!」
 
 全体的に甘口の発表ですが、辛口なところは唐辛子マーク を付けています。
 唐辛子マークの内容は TIPS みたいなものです。

  7. • 自己紹介
 • 本資料の対象者
 • Go ってなんだっけ
 • 静的型付けってなんだっけ
 •

    ポリモーフィズムってなんだっけ
 • Generics ってなんだっけ
 • Generics のなかった Go
 • まとめ
 AGENDA

  8. • ドリコムではもともと Ruby on Rails が主流だったけど
 • いまどき Ruby on

    Rails だけできててもヤバいだろってなった
 • 機会さえあれば Go を採用するようになった
 • ので、そんなに歴史は深くない
 
 という背景から、社内ではサーバ用言語としての文脈が主です。
 とはいえクライアントの観点からでもポテンシャルに溢れています。
 Go in Drecom

  9. • 自己紹介
 • 本資料の対象者
 • Go ってなんだっけ
 • 静的型付けってなんだっけ
 •

    ポリモーフィズムってなんだっけ
 • Generics ってなんだっけ
 • Generics のなかった Go
 • まとめ
 AGENDA

  10. JavaScript (動的型付け言語)
 const dog = {}; dog.name = “John” dog.greet

    = () => “bowwow”; 何が入るかも決まっていない空のオブジェクトに、
 後から自由にプロパティを定義できる。

  11. JavaScript (動的型付け言語)
 const dog = {}; dog.name = “John” dog.greet

    = () => “bowwow”; 何が入るかも決まっていない空のオブジェクトに、
 後から自由にプロパティを定義できる。
 JavaScript は動的型付け言語ですが、こ れを静的型付けっぽく記述してみると・・・

  12. JavaScript (動的型付け言語)
 const dog = {}; dog.name = “John” dog.greet

    = () => “bowwow”; 静的型付け風に書いた JavaScript
 const dog = { name: “”, greet: () => {}, }; dog.name = “John”; dog.greet = () => “bowwow”; 何が入るかも決まっていない空のオブジェクトに、
 後から自由にプロパティを定義できる。

  13. JavaScript (動的型付け言語)
 const dog = {}; dog.name = “John” dog.greet

    = () => “bowwow”; 静的型付け風に書いた JavaScript
 const dog = { name: “”, greet: () => {}, }; dog.name = “John”; dog.greet = () => “bowwow”; 何が入るかも決まっていない空のオブジェクトに、
 後から自由にプロパティを定義できる。
 オブジェクト定義の時点で、予め name や greet というプロ パティを定義し、想定する型のゼロ値をアサインする。

  14. JavaScript (動的型付け言語)
 const dog = {}; dog.name = “John” dog.greet

    = () => “bowwow”; 静的型付け風に書いた JavaScript
 const dog = { name: “”, greet: () => {}, }; dog.name = “John”; dog.greet = () => “bowwow”; // でもこういうことできちゃう dog.offend = () => “bark!”; dog.name = [“John”, “Joe”]; 何が入るかも決まっていない空のオブジェクトに、
 後から自由にプロパティを定義できる。

  15. 静的型付け風に書いた JavaScript
 const dog = { name: “”, greet: ()

    => {}, }; dog.name = “John” dog.greet = () => “bowwow”; // でもこういうことできちゃう dog.offend = () => “bark!”; dog.name = [“John”, “Joe”];
  16. 静的型付け風に書いた JavaScript
 const dog = { name: “”, greet: ()

    => {}, }; dog.name = “John” dog.greet = () => “bowwow”; // でもこういうことできちゃう dog.offend = () => “bark!”; dog.name = [“John”, “Joe”]; もしもそれができちゃうのが問題と感じる のなら、その時は静的型付け言語の TypeScript の出番!

  17. 静的型付け風に書いた JavaScript
 const dog = { name: “”, greet: ()

    => {}, }; dog.name = “John” dog.greet = () => “bowwow”; // でもこういうことできちゃう dog.offend = () => “bark!”; dog.name = [“John”, “Joe”]; もしもそれができちゃうのが問題と感じる のなら、その時は静的型付け言語の TypeScript の出番!
 できない!させない!

  18. コラム: AppStore でリリースするアプリバイナリは AOT じゃないとダメ。 
 実行時に振る舞いが変えられるようなものはセキュリティ観点から NG です。 •

    代表的な静的型付け言語は、AOT (Ahead Of Time) コンパイル
 • AOT コンパイルを通してプログラムを実行可能形式のバイナリにする
 ◦ C/C++ とか とか とか 
 • プログラム内の型に関する情報をコンパイル時点で検証する
 • 型に関する食い違いが検知されれば、コンパイルは失敗する
 おまけ: コンパイル

  19. おまけ: コンパイル
 • 反対に、プログラム実行中に逐次コンパイルを行うのが JIT (Just In Time) コンパ イル


    • 例えばコード上で静的に記述されたクラスに対してランタイムでメソッドを追加する などができる
 • JavaScript も、 V8 などの JavaScript エンジンによってランタイムで JIT コンパイ ルされている
 ◦ V8 は Chromium などに使われている Google 製 JavaScript エンジン 
 ◦ 興味があれば V8 の Hidden Class あたりを調べてみてください 
 • TypeScript のトランスパイルは AOT 的役割を果たすが、ランタイムは JavaScript

  20. • 自己紹介
 • 本資料の対象者
 • Go ってなんだっけ
 • 静的型付けってなんだっけ
 •

    ポリモーフィズムってなんだっけ
 • Generics ってなんだっけ
 • Generics のなかった Go
 • まとめ
 AGENDA

  21. ポリモーフィズム(多態性)
 function 水揚げ量():number { const fishes: おさかな[] = getFishes(); return

    fishes.length; } function マンボウの生息数():number { const fishes: [] = getFishes(“manbo”); return fishes.length; } 魚ならなんでもいい
 getFishes()
 マンボウが欲しい
 getFishes()

  22. ダックタイピング
 • greet したら、それが greet するもの
 • greet できればその実体はなんでも良い
 •

    関数利用者は greet するものを渡せばよい
 ポリモーフィズムの担保 (動的型付け言語での例)
 const dog = { greet: () => "bowwow", } const cat = { greet: () => "meow", } function polyGreet(someone) { console.log(someone.greet()); } polyGreet(dog); polyGreet(cat); 注) 左記はとりあえず greet を実行しているだけなので、意図的 なポリモーフィズムかどうかはコードからは読み取れない。 

  23. type Dog struct {} type Cat struct {} func (Dog)

    Greet() string { return “bowwow” } func (Cat) Greet() string { return “meow” } func polyGreet(maybeGreet interface{}) { // コンパイルエラー fmt.Printf("%s\n", maybeGreet.Greet()); } ダックタイピングを静的型付けの でやると・・・

  24. Go には「どんな型でも OK !」という意味の interface{} 型が 用意されており、引数型が決定的でない場合は interface{} での定義ができる
 type

    Dog struct {} type Cat struct {} func (Dog) Greet() string { return “bowwow” } func (Cat) Greet() string { return “meow” } func polyGreet(maybeGreet interface{}) { // コンパイルエラー fmt.Printf("%s\n", maybeGreet.Greet()); } ダックタイピングを静的型付けの でやると・・・
 しかし interface{} 型は、具体的な構造体のインターフェース を持たないためコンパイルエラーになる。
 (この厳格さは TypeScript の any とは異なる)

  25. 静的型付け言語でポリモーフィズムを担保する一般的な手法はある。
 
 • interface とその実装
 • generics
 • 抽象クラス &

    クラス継承
 • embedded (Go)
 • template (C++)
 
 静的型付け言語でのポリモーフィズム担保

  26. 静的型付け言語でポリモーフィズムを担保する一般的な手法はある。
 
 • interface とその実装
 • generics <- やっと出てきたな
 •

    抽象クラス & クラス継承
 • embedded (Go)
 • template (C++)
 
 静的型付け言語でのポリモーフィズム担保

  27. 静的型付けでも例外的にダックタイピング class Human { public string Greet() { return “hello”;

    } } public class Main : MonoBehavior { void ImplicitGreet(dynamic maybeGreet) { Debug.Log(maybeGreet.Greet()); } void Start() { ImplicitGreet(new Human()); } } class Human { public greet(): string { return "hello"; } } function implicitGreet(maybeGreet: any) { console.log(maybeGreet.greet()); } implicitGreet(new Human()); // hello TypeScript
 根は JavaScript なので。
 
 C#
 言語仕様としてサポート(?) 
 内部的に reflection で JIT コンパイル。 
 (C# 詳しくないです、すいません) 

  28. て、template… void greet(Dog* subject) { std::cout << subject->greet() << std::endl;

    } void greet(Cat* subject) { std::cout << subject->greet() << std::endl; } #include <iostream> class Dog { public: std::string greet() { return "bowwow"; } }; class Cat { public: std::string greet() { return "meow"; } }; template<class T> void greet(T subject) { std::cout << subject->greet() << std::endl; } int main(const int argc, const char** argv) { greet(new Dog()); greet(new Cat()); } こう書いたら
 こうなる(イメージ)
 C++ には template 呼ばれる仕様が用意さ れている。
 Generics のコンセプトが型の制約なら、 template のコンセプトは型に基づいて定義 を量産すること。(名前通り)
 結果的に、構文上はダックタイピングに見 える。

  29. • 自己紹介
 • 本資料の対象者
 • Go ってなんだっけ
 • 静的型付けってなんだっけ
 •

    ポリモーフィズムってなんだっけ
 • Generics ってなんだっけ
 • Generics のなかった Go
 • まとめ
 AGENDA

  30. Generics ってなんだっけ
 ありきたりな情報は ggrks
 私見を 3行で述べると
 • 型パラメータで多態性を一元的に利用できる仕様 (パラメトリック)
 •

    キャストするより全然マシ
 • interface をより強力にするモノ
 <MyComponent<MyType> /> どうでもいいけど、JSX だと 
 なんかちょっとモヤっとしません? 

  31. Interface だけじゃだめな時
 const repo: Repository = open(“mysql”); repo.put(obj); interface Repository

    { open(); put(); ... } class MySql implements Repository { ... show() { ... } } 抽象的で良い場合もあるが・・・

  32. Interface だけじゃだめな時
 const repo: Repository = open(“mysql”); repo.put(obj); interface Repository

    { open(); put(); ... } class MySql implements Repository { ... show() { ... } } const repo: Repository = open(“mysql”); // MySql 特異のメソッドを使いたいときは? repo.show(obj); 抽象的で良い場合もあるが・・・
 具体的に使いたいケースでは満たせない

  33. Interface だけじゃだめな時
 const repo: Repository = open(“mysql”); (repo as MySql).put(obj);

    const repo: MySql = openMySql(); const repo: MySql = open<MySql>(); キャストしたり個別関数を用意したり することでも回避はできる。
 Generics であればスマートにポリ モーフィズムを担保できる。

  34. Generics と interface を組み合わせると実装の意味が伝わりやすくなる。
 そして何より、コンパイル時間で検証される。
 組み合わせると強力!
 function open<T extends Repository>():

    T { const repo = createRepository<T>(); repo.open(); return repo; } • open の対象の性質が一目瞭然 
 • Repository の実装以外はコンパイルで 弾かれる
 const repo: MySql = open<MySql>(); repo.put(obj); • MySql という具体的な知識を持って いてよいことが分かる

  35. • 自己紹介
 • 本資料の対象者
 • Go ってなんだっけ
 • 静的型付けってなんだっけ
 •

    ポリモーフィズムってなんだっけ
 • Generics ってなんだっけ
 • Generics のなかった Go
 • まとめ
 AGENDA

  36. func Print(int v) { fmt.Printf(“%d\n”, v) } func Print(float v)

    { fmt.Printf(“%f\n”, v) } func Print(string v) { fmt.Printf(“%s\n”, v) } には generics がなかった!
 しかも同じ関数名は NG!
 func PrintInt(int v) { fmt.Printf(“%d\n”, v) } func PrintFloat(float v) { fmt.Printf(“%f\n”, v) } func PrintString(string v) { fmt.Printf(“%s\n”, v) } 涙ぐましい努力が必要!

  37. type Users []*User func (l Users) Find(id uint) *User {

    for _, user := range l { if user.GetID() == id { return user } } return nil } 複数型共通処理を
 type Books []*Book func (l Books) Find(id uint) *Book { for _, book := range l { if book.GetID() == id { return book } } return nil } 具体的な型で量産
 には generics がなかった!
 任意処理を複数型でも一元的に利用するシンプルな方法がなかった。
 (だからコード生成をするといソリューションが多いように感じる)

  38. func Find(id uint, items []interface{}) *interface{} { for _, item

    := range items { value := reflect.ValueOf(item) method := value.MethodByName("GetID") result := method.Call([]reflect.Value{}) if result[0].Uint() == id { return &item } } return nil } やろうと思えば interface{} & reflection でできるけど
 コンパイル時間で問題が検知できないのでとてもやりたくない。
 e.g) メソッド名が変わったり返り値の型が変わっても検知できない

  39. var users Records[User] ... user := users.Find(uint(1)) fmt.Printf("%v\n", user.GetID()) それが今じゃあ超絶楽ちん


    type Record interface { GetID() uint } type Records[T Record] []*T func (l Records[T]) Find(id uint) *T { for _, item := range l { if (*item).GetID() == id { return item } } return nil } 一回書くだけで OK !

  40. の Generics は 1.18 から
 2月に 1.18 はリリースされる予定だったけど、まだ RC。
 docker

    で 1.18 RC イメージはあるので気軽に試してみよう。
 
 https://hub.docker.com/_/golang

  41. まとめ
 • AOT コンパイルにおいて型は厳格にチェックされる
 • 型のポリモーフィズム表現手法として interface や generics がある


    • interface では具体的な型を扱いたい場合に苦しい場面がある
 • 具体的な型として利用する手法に generics がある
 • Go には generics がなかった
 • しかし遂に go 1.18 で generics が提供予定!