OCamlのlambda IRをいじる(レコード)
レコード型を定義して使うコードがどのようにlambda IRにコンパイルされるかを見ていく。
定義して使う
簡単なレコード型を定義した上でa.xなどとアクセスして使ってみる:
type t = { x : int; y : string } let () = let a = {x=1; y="hello"} in print_endline @@ a.y ^ (string_of_int a.x)
-dlambdaでコンパイル:
(seq
(let
(*match*/87 =
(let (a/83 = [0: 1 "hello"])
(apply (field 45 (global Stdlib!))
(apply (field 27 (global Stdlib!)) (field 1 a/83)
(apply (field 32 (global Stdlib!)) (field 0 a/83))))))
0a)
0a)
lambda IRでは型は消去されているので、type t = ...の部分は完全に消えている。
その上でレコードはデータとしてはタプルとまったく同じ表現になっている。a.xなどのフィールドアクセスは(field 0 a/83)といったタプル要素のインデックスアクセスにコンパイルされる。
withでコピー
レコードのwith構文で、部分的変更を加えたコピーをしてみる:
type t = { x : int; y : string; z : bool } let () = let a = {x=1; y="hello"; z=true } in let b = {a with y="hi"} in print_endline @@ a.y ^ b.y
-dlambdaでコンパイル:
(seq
(let
(*match*/90 =
(let
(a/84 = [0: 1 "hello" 1a]
b/85 = (makeblock 0 (int,*,*) (field 0 a/84) "hi" (field 2 a/84)))
(apply (field 45 (global Stdlib!))
(apply (field 27 (global Stdlib!)) (field 1 a/84) (field 1 b/85)))))
0a)
0a)
let b = {a with y="hi"}が(makeblock 0 (int,*,*) (field 0 a/84) "hi" (field 2 a/84))になっている。
makeblockで[0: 1 "hello" 1a]のようなタプル的データ構造を作る。後者はlambda IRにおけるデータ構造リテラルなのだろう。要素がリテラルでない場合はmakeblockで作成する形にコンパイルされるようだ。
(makeblock 0 ...の0はデータのタグ([0: ...]の0:部分)で、それに続く(int,*,*)は各要素の大まかな型でboxされない整数かポインタか、を差別化している。
それ以降は実際にデータ構造に含まれる要素の羅列。
タプルでmakeblock
確認のためリテラルではない要素が含まれるタプルがどうコンパイルされるか見てみる:
let () = let a = 1 in let x = (a, 2) in let y = (1+2, 4) in print_int (snd x + snd y)
-dlambdaでコンパイル:
(seq
(let
(*match*/84 =
(let
(a/80 =[int] 1
x/81 = (makeblock 0 (int,int) a/80 2)
y/82 = (makeblock 0 (int,int) (+ 1 2) 4))
(apply (field 43 (global Stdlib!))
(+ (field 1 x/81) (field 1 y/82)))))
0a)
0a)
タプルの場合も変数や非リテラル式が要素になっている場合、[0: ...]という形ではなくmakeblockにコンパイルされることが確認できる。
次回
次は代数的データ型とそれに対するパターンマッチのコンパイルについて。