Arantium Maestum

プログラミング、囲碁、読書の話題

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)などとしている(これはモジュールのフィールドをアクセスする方法としてすでに既出なことに注意)。

fstsndが関数適用として現れてこないのが面白い。直接(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/81b/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 さんから以下のとおりご教示いただいた。ありがとうございます!