Slide 1

Slide 1 text

1 / 42 VRCLT #2 型推論を支える技術 cannorin

Slide 2

Slide 2 text

2 / 42 誰 ● Twitter: @cannorin_vrc ● Study: 数理論理学,プログラム言語の理論 ● Job: F# プログラマ(アルバイト)

Slide 3

Slide 3 text

3 / 42 不毛な型推論バトル ● C#/Java の var みたいなやつでしょ ● Python も型書かなくていいじゃん ● そんな最近出てきた機能知らない

Slide 4

Slide 4 text

4 / 42 不毛な型推論バトル ←こわい ● C#/Java の var みたいなやつでしょ → あいつらはクソザコ! ● Python も型書かなくていいじゃん → 動的型付けと一緒にするな! ● そんな最近出てきた機能知らない → 昔からあるし!

Slide 5

Slide 5 text

5 / 42 なぜ繰り返されるのか ● 型推論とは一体なんなのか理解されていない ● 型推論の仕組みが知られてない ● わからないものは怖がられがち

Slide 6

Slide 6 text

6 / 42 なぜ繰り返されるのか ● 型推論とは一体なんなのか理解されていない (動的型付けとの違いがわからない) ● 型推論の仕組みが知られてない ( C#/Java の var との違いがわからない) ● わからないものは怖がられがち (一般にアカデミアの人間は怖がられがち)

Slide 7

Slide 7 text

7 / 42 → 今回の目標! ● 型推論とは一体なんなのか説明する ● 型推論の仕組みを簡単に解説する ● わかったつもりになってもらう

Slide 8

Slide 8 text

8 / 42 1 型推論ってそもそも何? ● 主に静的型付き言語をターゲットとする

Slide 9

Slide 9 text

9 / 42 1 型推論ってそもそも何? ● 主に静的型付き言語をターゲットとする ● 変数・関数の型がほとんど or 全く書かれていない コードに

Slide 10

Slide 10 text

10 / 42 1 型推論ってそもそも何? ● 主に静的型付き言語をターゲットとする ● 変数・関数の型がほとんど or 全く書かれていない コードに ● もっとも汎用的な正しい型を当てはめる手法

Slide 11

Slide 11 text

11 / 42 1.1 要するに let-function add_two (x : int) : int = + x 2 let-function apply_twice (f : int → int) (x : int) : int = f (f x) let-value foo : int = apply_twice add_two 0 もともとはこういう言語

Slide 12

Slide 12 text

12 / 42 1.1 要するに let-function add_two (x : int) : int = + x 2 let-function apply_twice (f : int → int) (x : int) : int = f (f x) let-value foo : int = apply_twice add_two 0 プログラマが型を書かなくても

Slide 13

Slide 13 text

13 / 42 1.1 要するに let-function add_two (x : int) : int = + x 2 let-function apply_twice (f : int → int) (x : int) : int = f (f x) let-value foo : int = (←C#/Java はここしかできない ) apply_twice add_two 0 自動で最適な型をつけてくれる

Slide 14

Slide 14 text

14 / 42 1.2 型推論の特長 ● 主に静的型付き言語をターゲットとする ● 変数・関数の型がほとんど or 全く書かれていない コードに ● もっとも汎用的な正しい型を当てはめる手法

Slide 15

Slide 15 text

15 / 42 1.2 型推論の特長 ● 主に静的型付き言語をターゲットとする → 厳格な型チェックによる利点はそのまま ● 変数・関数の型がほとんど or 全く書かれていない コードに → 複雑な型になるコードでも簡単に書ける ● もっとも汎用的な正しい型を当てはめる手法 → 失敗しないし信頼できる(ように作る必要がある)

Slide 16

Slide 16 text

16 / 42 1.3 型推論の短所 ● コンパイル時間が長くなる ● 言語の型システムの機能が強すぎると無理 ● 動的型付けと見た目は似てるが,動的型付き言語に あとから追加するのは大変難しい!

Slide 17

Slide 17 text

17 / 42 1.3 型推論の短所 ● コンパイル時間が長くなる (そのぶん動作は高速だしバグも減らせるけど) ● 言語の型システムの機能が強すぎると無理 (理論的限界.一部だけ書かせるとできたりする) ● 動的型付けと見た目は似てるが,動的型付き言語に あとから追加するのは大変難しい! (そもそもベースが静的型付けなので……)

Slide 18

Slide 18 text

18 / 42 2 型推論の仕組み ● 文脈(=変数・関数名とその型の辞書)と 推論規則(=制約を生成するルール)を用意する

Slide 19

Slide 19 text

19 / 42 2 型推論の仕組み ● 文脈(=変数・関数名とその型の辞書)と 推論規則(=制約を生成するルール)を用意する ● コード中の未知の型変数を文脈に追加し,推論規則 を用いて型の等値制約(≒型の等式)を生成する

Slide 20

Slide 20 text

20 / 42 2 型推論の仕組み ● 文脈(=変数・関数名とその型の辞書)と 推論規則(=制約を生成するルール)を用意する ● コード中の未知の型変数を文脈に追加し,推論規則 を用いて型の等値制約(≒型の等式)を生成する ● 得られた制約列を単一化して,文脈に追加した 型変数を消す(≒型の連立方程式の解を求める)

Slide 21

Slide 21 text

21 / 42 2 型推論の仕組み ● 文脈(=変数・関数名とその型の辞書)と 推論規則(=制約を生成するルール)を用意する ● コード中の未知の型変数を文脈に追加し,推論規則 を用いて型の等値制約(≒型の等式)を生成する ● 得られた制約列を単一化して,文脈に追加した 型変数を消す(≒型の連立方程式の解を求める) ● 必要な結果を文脈に残して次のコードに進む

Slide 22

Slide 22 text

22 / 42 2.1 実際にやってみよう ● 文脈(=変数・関数名とその型の辞書)と 推論規則(=制約を生成するルール)を用意する – 文脈: ● + は int 型 2 つを取って int 型を返す ( +: int → int → int ) – 推論規則: ● 整数リテラル 0,1,2... は int 型 ● 文脈に関数 f: a → b, 値 x: a があるとき 関数適用 f x は b 型 ● etc...

Slide 23

Slide 23 text

23 / 42 2.1 実際にやってみよう ● コード中の未知の型変数を文脈に追加し,推論規則 を用いて型の等値制約(≒型の等式)を生成する let-function add_two x = + x 2 制約 文脈 + : int → int → int ※ 未知の関数・変数に対して,他と重複 しないように型変数を生成し,文脈に追加する

Slide 24

Slide 24 text

24 / 42 2.1 実際にやってみよう ● コード中の未知の型変数を文脈に追加し,推論規則 を用いて型の等値制約(≒型の等式)を生成する let-function add_two x = + x 2 制約 文脈 + : int → int → int, add_two : a → b ※ 未知の関数・変数に対して,他と重複 しないように型変数を生成し,文脈に追加する

Slide 25

Slide 25 text

25 / 42 2.1 実際にやってみよう ● コード中の未知の型変数を文脈に追加し,推論規則 を用いて型の等値制約(≒型の等式)を生成する let-function add_two x = + x 2 制約 文脈 + : int → int → int, add_two : a → b, x : c ※ 未知の関数・変数に対して,他と重複 しないように型変数を生成し,文脈に追加する

Slide 26

Slide 26 text

26 / 42 2.1 実際にやってみよう ● コード中の未知の型変数を文脈に追加し,推論規則 を用いて型の等値制約(≒型の等式)を生成する let-function add_two x = + x 2 制約 a = c 文脈 + : int → int → int, add_two : a → b, x : c

Slide 27

Slide 27 text

27 / 42 2.1 実際にやってみよう ● コード中の未知の型変数を文脈に追加し,推論規則 を用いて型の等値制約(≒型の等式)を生成する let-function add_two x = + x 2 制約 a = c, c = int 文脈 + : int → int → int, add_two : a → b, x : c

Slide 28

Slide 28 text

28 / 42 2.1 実際にやってみよう ● コード中の未知の型変数を文脈に追加し,推論規則 を用いて型の等値制約(≒型の等式)を生成する let-function add_two x = + x 2 制約 a = c, c = int, int = int 文脈 + : int → int → int, add_two : a → b, x : c

Slide 29

Slide 29 text

29 / 42 2.1 実際にやってみよう ● コード中の未知の型変数を文脈に追加し,推論規則 を用いて型の等値制約(≒型の等式)を生成する let-function add_two x = + x 2 制約 a = c, c = int, int = int, b = int 文脈 + : int → int → int, add_two : a → b, x : c

Slide 30

Slide 30 text

30 / 42 2.1 実際にやってみよう ● 得られた制約列を単一化して,文脈に追加した 型変数を消す(≒型の連立方程式の解を求める) let-function add_two x = + x 2 制約 a = c, c = int, int = int, b = int 文脈 + : int → int → int, add_two : a → b, x : c

Slide 31

Slide 31 text

31 / 42 2.1 実際にやってみよう ● 得られた制約列を単一化して,文脈に追加した 型変数を消す(≒型の連立方程式の解を求める) let-function add_two x = + x 2 制約 a = int, b = int, c = int 文脈 + : int → int → int, add_two : a → b, x : c

Slide 32

Slide 32 text

32 / 42 2.1 実際にやってみよう ● 得られた制約列を単一化して,文脈に追加した 型変数を消す(≒型の連立方程式の解を求める) let-function add_two x = + x 2 制約 a = int, b = int, c = int 文脈 + : int → int → int, add_two : a → b, x : c

Slide 33

Slide 33 text

33 / 42 2.1 実際にやってみよう ● 得られた制約列を単一化して,文脈に追加した 型変数を消す(≒型の連立方程式の解を求める) let-function add_two x = + x 2 制約 文脈 + : int → int → int, add_two : int → int, x : int

Slide 34

Slide 34 text

34 / 42 2.1 実際にやってみよう ● 必要な結果を文脈に残して次のコードに進む ( 実装上は,スコープの中に入る前に文脈のバックアップを取り, 結果のうちスコープの外に出るものだけを外側の文脈に追加する ) 制約 文脈 + : int → int → int, add_two : int → int, x : int ↓ ローカル変数(引数)

Slide 35

Slide 35 text

35 / 42 2.1 実際にやってみよう ● 必要な結果を文脈に残して次のコードに進む ( 実装上は,スコープの中に入る前に文脈のバックアップを取り, 結果のうちスコープの外に出るものだけを外側の文脈に追加する ) 制約 文脈 + : int → int → int, add_two : int → int

Slide 36

Slide 36 text

36 / 42 2.2 Hindley-Milner 型推論 ● 例えば は関数 f がどんな型であっても使えるはず ● このような関数に対して自動的に全称型を付ける(= ジェネリック型にする)ように設計された アルゴリ ズムが Hindley-Milner 型推論 ● 中身は先ほど説明した仕組みとほぼ同じで,全称型付 きの関数を文脈に記録する際に工夫をする let-function apply_twice f x = f (f x)

Slide 37

Slide 37 text

37 / 42 2.2 Hindley-Milner 型推論 ● Hindley-Milner を使うと,先述の例には という型をつけることができる ● Hindley-Milner の歴史は古く,だいたい 1970 年代ま で遡ることができる ● まともな型推論を積んでいる現代の言語は,ほぼ 例外 なく Hindley-Milner かその拡張を使っている let-function apply_twice (f : T → T) (x : T) : T = f (f x)

Slide 38

Slide 38 text

38 / 42 2.3 制約単一化型推論の限界 ● 型の等値制約を単一化するタイプの型推論アルゴリズ ムは,関数・演算子のオーバーロードに対応できない – 解が複数存在する(が全称型にならない)ので 「一番汎用的な型」がそもそも存在しない – ユーザが自作型に演算子を定義できるような場合, 解がいくつあるのかすら分からない! – Hindley-Milner にオーバーロード解決を加えると 決定不能になる(=止まらないかもしれない)

Slide 39

Slide 39 text

39 / 42 2.3 制約単一化型推論の限界 ● ほぼ同じ理由で,オブジェクト指向プログラミングで よく使われる機能への対応が難しい – 同じ名前のメンバやメソッドを持つ型がたくさん存 在しうるので,それらの使い方から解が定まらない – 継承などの機能で部分型が導入されると,推論結果 の汎用性に部分型関係が絡んできて難しい – 型の解がいくつあるのか分からなくなるような機能 はだいたいヤバイ

Slide 40

Slide 40 text

40 / 42 2.4 Further Reading ● LT の枠に収めるために端折ったり誤魔化したりした 所が結構ある…… 特に型推論の限界はもっと色々ある ● 興味があって詳しく知りたい・実装してみたい方は B. C. Pierce 「型システム入門」を読んでみてね↓ ● prog-lang-sys-ja.slack.com の #theory チャンネルに来てもらえれば,込み入った 質問にも随時対応できますので是非〜

Slide 41

Slide 41 text

41 / 42 Thank You! ● 分かりやすく説明できてたら嬉しいです ● 分かっちゃうと怖くない.怖くなくない? ● 型推論で幸せになりたいなら F# っていう言語がおすすめです

Slide 42

Slide 42 text

42 / 42 質問タイム(?)