この記事はML Advent Calendar 2020 15日目の記事です。
OCamlにそろそろ入りそうな機能を紹介します。
今回は、次のOCaml 4.12で入る Add primitive __FUNCTION__ that returns the name of current scope by nojb · Pull Request #9554 · ocaml/ocaml の紹介をします。
OCamlのStdlib
には__FILE__
, __LINE__
, __POS__
, __LOC__
, __MODULE__
といった、現在のソースコード上の位置や、現在のモジュール名を取得するための値が用意されている。
このPRではさらに、 __FUNCTION__
という、現在のスコープ名を文字列として取得する値を導入する。このとき得られる文字列は、OCaml 4.11.0以降でバックトレースに表示される関数名と同じ形式だ(Print function names in backtraces by stedolan · Pull Request #9096 · ocaml/ocaml を参照)。ちなみに、 __FUNCTION__
という名前にもかかわらず、現在定義中の変数の名前や、メソッドの名前も取ることができる。
具体的にどのような名前が得られるかを見てみよう。
let f = __FUNCTION__
module Mod1 = struct
module Nested = struct
let f () = __FUNCTION__
end
end
let anon () =
Fun.id (fun () -> __FUNCTION__) ()
let local_module () =
let module N = struct
let foo () = __FUNCTION__
end
in N.foo ()
class klass = object
method meth () = __FUNCTION__
end
let () =
print_endline @@ f;
(*
Locs.f
変数定義の右辺の場合は、現在定義中の変数名が取れる。
先頭のLocsはこのファイルがLocsモジュールになることに由来する。以下同じ
*)
print_endline @@ Mod1.Nested.f ();
(*
Locs.Mod1.Nested.f
モジュールを入れ子にするとそれがパス風に付加される
*)
print_endline @@ anon ();
(*
Locs.anon.(fun)
関数anon内の無名関数。無名関数は (fun) で表現する
*)
print_endline @@ local_module ();
(*
Locs.local_module.N.foo
関数 local_module 内のローカルモジュールN、さらにその中の関数foo
*)
print_endline @@ (new klass)#meth ();
(*
Locs.klass#meth
klassクラスのメソッドmeth
*)
()
モジュール内の要素を参照するときのモジュールパスと似たような形式になっている。ocaml/testsuite/tests/translprim/locs.ml, ocaml/testsuite/tests/translprim/locs.reference にはより複雑な例がある。
ちなみに、この機能はコンパイラプリミティブとして提供されているので、ユーザーレベルで同じような機能を実現することはできない。おおまかな流れとしては以下のように Typedtree
から Lambda
に致る段階で識別子の参照から文字列定数へ置き換えられている(Parsetree
、Typedtree
、Lambda
はコンパイラの中間表現の名前)。
- ソースコード:
__FUNCTION__
↓ 構文解析 - Parsetree:
Pexp_ident "__FUNCTION__"
↓ 型推論・型検査 - Typedtree:
Texp_ident "Stdlib!.__FUNCTION__"
↓ パターンマッチのコンパイル。モジュール、クラスの除去。__FUNCTION__
等プリミティブの変換 - Lambda:
Lconst (Const_immstring "Locs.f")
↓ バイトコード/ネイティブコード生成へ - …
各ステップの様子は ocamlc -dparsetree -dtypedtree -dlambda
のようにしてコンパイラを実行すると見ることができる。