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

Go に Generics がやってきた

4e59283eae97be500d538af3bf036c23?s=47 Smith
March 13, 2022

Go に Generics がやってきた

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

4e59283eae97be500d538af3bf036c23?s=128

Smith

March 13, 2022
Tweet

More Decks by Smith

Other Decks in Technology

Transcript

  1. Go に Generics がやってきた!
 Smith
 [
 ]


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

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

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

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

  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. @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 ??

  6. ミッション
 プロダクトのクライアント技術領域における信頼性向上。
 (とはいえ、1~2名体制なので出来る範囲で)
 よくある業務
 • 協業パートナー技術選定
 • メンバー不定のプロジェクトの技術ディレクション
 • 及びテクニカルスパイクの解決


    SRE クライアントグループ?

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

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

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

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

  9. 本発表を通して、下記をそこはかとなく感じていただければ幸いです。
 • インターフェースや多態性の必要性
 • 静的型付け言語ならではの多態性表現
 • 言語仕様としての表現力の大切さ
 本発表の主な対象者


  10. Go に Generics がやってきた!
 [
 ]


  11. でも、Go の話はほとんどないです!
 [
 ]
 ブフォ


  12. Go に Generics がやってきた!
 
 
 


  13. Go に Generics がやってきた!
 
 けど特別なことは何もなかったので、 
 静的型付けとかポリモーフィズムとか interface あたりについておさらいしようじゃないか

  14. 「・・・」


  15. 「Go ってなに?」


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

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

  17. ありきたりな情報は ggrks
 個人的に好きなところを 3つ挙げると・・・
 Go ってなんだっけ
 1. もちろん、静的型付けなところ!
 2. クロスコンパイルが超絶楽ちん!


    3. defer, goroutine, channel とかのゆとり厨二要素が沢山!

  18. Go にはこんな使い方もあるぞ
 • switch 対応で go2cpp が生み出された
 • WASM 以外のシングルスレッドのランタイムにも期待

    (なお需要)
 https://tech.drecom.co.jp/ac2021-go-cgo-c-php/

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

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

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

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

  21. 「・・・」


  22. 「静的型付けってなに?」


  23. 逆に動的型付けから見ていこう


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

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

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

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

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

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

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

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

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

  29. 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”]; 何が入るかも決まっていない空のオブジェクトに、
 後から自由にプロパティを定義できる。

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

    => {}, }; dog.name = “John” dog.greet = () => “bowwow”; // でもこういうことできちゃう dog.offend = () => “bark!”; dog.name = [“John”, “Joe”];
  31. Q. できちゃうことの何がダメなの?
 A. そのオブジェクトが確実に持っている プロパティやその型がわからない。
 関数の引数などで間接的にオブジェ クトが受け渡された時、利用したいプ ロパティをいちいち検証しなければな らない。
 静的型付け風に書いた

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

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

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

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

  34. ちょうざっくりまとめ
 • 静的型付けではプログラム中で扱うデータ構造やデータ型は予め宣言する
 • 宣言に違反する場合はコンパイル時にエラーを出してくれる
 • これすなわち型安全
 • (TypeScript がこんなに普及しているとは、つまりそういうことだ)


    静的型付け
 • 動的型付け言語でプログラミングできるエンジニアはすごいと思う

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

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

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


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

  37. • 動的型付けは好きなタイミングで好きなようにオブジェクトを定義できる
 • 静的型付けでは予め宣言された型の使い方以外は許容できない
 • 静的型付けは実行前に予め実行可能形式にコンパイルする
 • 動的型付けと静的型付けではプログラミングの書き方が大きく変わる
 
 次項からポリモーフィズムを例に、もう少し違いを深堀りしていこう。


    徐々に本日の主題の Generics に近づいてくぞ。
 2つのタイプの言語を見比べてざっくりわかったこと

  38. 「・・・」


  39. 「ポリモ・・・なに?」


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

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

  41. ポリモーフィズム(多態性)
 おさかな
 • 泳ぐ
 • 卵を生む
 • エラ呼吸する


  42. ポリモーフィズム(多態性)
 カクレクマノミです


  43. ポリモーフィズム(多態性)
 カクレクマノミです
 エイです


  44. ポリモーフィズム(多態性)
 カクレクマノミです
 エイです
 マンボゥォェェ--!!!


  45. ポリモーフィズム(多態性)
 「おさかな」
 カクレクマノミです
 エイです
 マンボゥォェェ--!!!


  46. ポリモーフィズム(多態性)
 プログラム的には
 
 • 「おさかな」 → interface
 • 「マンボゥォェェーー!!!」 →

    interface の実装
 

  47. ポリモーフィズム(多態性)
 ユースケース的には
 
 • 「おさかな」 → 魚なら何でもいい時
 • 「マンボゥォェェーー!!!」 →

    マンボウじゃないとダメな時
 

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

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

  49. ポリモーフィズムとは・・・
 インターフェースに準拠した多種多様な定義があること。
 e.g) おさかなというインターフェースの特性を満たした
 カクレクマノミ、エイ、マンボウ
 
 仕事の内容によって求められるおさかなの具体性が異なるユースケース が共存する時、ポリモーフィズムの担保が求められる。
 ポリモーフィズム(多態性)


  50. ポリモーフィズムの担保とは
 インターフェーズに基いた多態性を実現しつつ、多態的なエンティティや 関数、クラスなどを一元的に利用可能にすること
 ポリモーフィズムの担保
 const = getFIsh(“nemo”); const = getFIsh(“ei”);

    const = getFIsh(“manbo”); putFish( ); putFish( ); putFish( ); 要は、一個の関数で沢山の型が使えたらいいよね、ってのを実現するなどのこと。 

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

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

  52. 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()); } ダックタイピングを静的型付けの でやると・・・

  53. 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 とは異なる)

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

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

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

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

  56. (ちょっと話逸れるけど)


  57. 静的型付けでも例外的にダックタイピング 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# 詳しくないです、すいません) 

  58. て、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 のコンセプトは型に基づいて定義 を量産すること。(名前通り)
 結果的に、構文上はダックタイピングに見 える。

  59. (意外とできたわ、ごめん)
 型破りなので、型を覚えてから適切に使ってください。 
 C++ の template はまたちょっと違う派閥です。 


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

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

  61. 関数や構造体が型パラメーターを持てること。
 型パラメーターを通して単一の関数や構造体を複数の型で実行できること。
 Generics ってなんだっけ
 function print<T>(v: T) { console.log(v); }

    宣言側
 const i: number = 123; print<number>(i); const s: string = “abc”; print<string>(s); 利用者側

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

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

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

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

  64. 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); 抽象的で良い場合もあるが・・・
 具体的に使いたいケースでは満たせない

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

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

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

    T { const repo = createRepository<T>(); repo.open(); return repo; } const repo: MySql = open<MySql>(); repo.put(obj);
  67. 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 という具体的な知識を持って いてよいことが分かる

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

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

  69. やっと Go のはなし


  70. 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) } 涙ぐましい努力が必要!

  71. 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 がなかった!
 任意処理を複数型でも一元的に利用するシンプルな方法がなかった。
 (だからコード生成をするといソリューションが多いように感じる)

  72. 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) メソッド名が変わったり返り値の型が変わっても検知できない

  73. 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 !

  74. Generics, 来てくれてありがとう!
 [
 ]


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

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

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


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

  77. ご清聴ありがとうございました
 [
 ]