OCamlのlambda IRをいじる(タプル)
タプルとそれに対するパターンマッチがどうlambda IRにコンパイルされるかを見ていく。
fst/snd
まずは非常に簡単なタプルと、その値をfst
/snd
でアクセスするコード:
let () = let x = (1,2) in print_int @@ (fst x) + (snd x)
-dlambda
でコンパイル:
(seq (let (*match*/82 = (let (x/80 = [0: 1 2]) (apply (field 43 (global Stdlib!)) (+ (field 0 x/80) (field 1 x/80))))) 0a) 0a)
(1, 2)
が[0: 1 2]
になっているのがわかる。0:
の意味はADTについて調べるときにわかるのでいったん置いておくとして、それ以降が格納されている値だ。そしてその最初の要素にアクセスするのに(field 0 x)
、第二要素にアクセスするのに(field 1 x)
などとしている(これはモジュールのフィールドをアクセスする方法としてすでに既出なことに注意)。
fst
やsnd
が関数適用として現れてこないのが面白い。直接(field 0 x/80)
のようにデータ構造に対するフィールドアクセスにコンパイルされている。
最適化なしだとどうなるか?-drawlambda
で試してみると:
(seq (let (*match*/82 = (let (x/80 = [0: 1 2]) (dirapply (field 43 (global Stdlib!)) (+ (field 0 x/80) (field 1 x/80))))) 0a) 0a)
まったく変わらないlambda IRになる。
letによるパターンマッチ
fst
/snd
を使わず、let (a,b) = ...
のような形でパターンマッチしてみる:
let () = let x = (1, 2) in let (a, b) = x in print_int @@ a + b
-dlambda
でコンパイル:
(seq (let (*match*/86 = (let (x/80 = [0: 1 2]) (apply (field 43 (global Stdlib!)) (+ (field 0 x/80) (field 1 x/80))))) 0a) 0a)
さきほどとまったく同じlambda IRになっている。
-drawlambda
だと:
(seq (let (*match*/86 = (let (x/80 = [0: 1 2] b/82 =a (field 1 x/80) a/81 =a (field 0 x/80)) (dirapply (field 43 (global Stdlib!)) (+ a/81 b/82)))) 0a) 0a)
ちゃんとa/81
とb/80
という変数が出てくる。ちなみにlet (a, b) = ...
なのだがlet
にコンパイルされると順序が入れ替わっているのも興味深い。OCamlの標準実装だとタプルの要素の評価順が右側からなので当然ではあるのだが。
またb/82 =a (field 1 x/80)
などで=a
という構文が出てくるのも面白い。なぜ=[int]
ではダメなのかが不思議だが・・・
matchによるパターンマッチ
match
構文でのパターンマッチ:
let () = let x = (1,2) in match x with (a,b) -> print_int @@ a + b
-dlambda
だと前述の例二つとまったく同一になる:
(seq (let (*match*/84 = (let (x/80 = [0: 1 2]) (apply (field 43 (global Stdlib!)) (+ (field 0 x/80) (field 1 x/80))))) 0a) 0a)
面白いのは-drawlambda
だとlet
によるパターンマッチと同じ表現になること:
(seq (let (*match*/84 = (let (x/80 = [0: 1 2] b/82 =a (field 1 x/80) a/81 =a (field 0 x/80)) (dirapply (field 43 (global Stdlib!)) (+ a/81 b/82)))) 0a) 0a)
タプルだとパターンが一つしかありえないので同じ表現になる。
違う型が含まれるタプル
タプルに含まれる要素は型が違っていても問題ない:
let () = let x = (true, 1, "hello") in let (a,b,c) = x in if a then print_int b else print_endline c
-dlambda
でコンパイル:
(seq (let (*match*/89 = (let (x/80 = [0: 1a 1 "hello"]) (if (field 0 x/80) (apply (field 43 (global Stdlib!)) (field 1 x/80)) (apply (field 45 (global Stdlib!)) (field 2 x/80))))) 0a) 0a)
[0: 1a 1 "hello"]
といろんな型の要素が問題なく[0: ...]
というデータ構造の中に入ることがわかる。
次回
次はリストがどうコンパイルされるかをみてみる。
追記
=a
に関して@camloeba さんから以下のとおりご教示いただいた。ありがとうございます!
=a は alias ですね。副作用起きないので変数使用を右辺定義に展開してもオッケーなやつです
— Jun Furuse 🐫🌴 (@camloeba) May 10, 2021