リフレクションとイントロスぺクション

Julia は様々な実行時リフレクションの機能を提供します。

モジュールが持つ束縛

Module がエクスポートする名前は names(m::Module) で取得できます。この関数は Symbol の配列を返し、それぞれがエクスポートされた束縛を表します。names(m::Module, all = true) とするとエクスポートの状態に関わらず m に含まれる全ての束縛が返ります。

DataType のフィールド

DataType が持つフィールドの名前は fieldnames で取得できます。例えば次の Point 型に対する fieldnames(Point) はフィールド名 x, y を表す Symbol のタプルを返します:

julia> struct Point
           x::Int
           y
       end

julia> fieldnames(Point)
(:x, :y)

Point オブジェクトに含まれる各フィールドの型は変数 Pointtypes フィールドに格納されます:

julia> Point.types
svec(Int64, Any)

Point 型の定義で xInt という型注釈が付いていますが、y には型注釈が付いていません。そのため y の型はデフォルトの Any となります。

型自身は DataType という構造体で表現されます:

julia> typeof(Point)
DataType

fieldnames(DataType) とすると DataType のフィールドを確認できますが、その中に上の例で見た types というフィールドがあることに注目してください。

部分型

DataType が持つ直接の部分型からなる配列は subtypes で取得できます。例えば AbstractFloat という DataType は四つの部分型を持ちます (たまたま全て具象型です):

julia> subtypes(AbstractFloat)
4-element Array{Any,1}:
 BigFloat
 Float16
 Float32
 Float64

部分型に抽象型があれば、それも subtypes が返す配列に加えられます。ただし、部分抽象型のそのまた部分型は無視されます: 型ツリーを完全に調べるには subtypes を再帰的に適用してください。

DataType のレイアウト

C コードとやり取りするとき DataType の内部表現は非常に重要であり、この詳細を調べるための関数がいくつか利用可能です。isbits(T::DataType)T 型の値が C 互換のアライメントで格納されるときに true を返します。fieldoffset(T::DataType, i::Integer) は型 T の最初から数えて i 番目のフィールドに対する (バイト) オフセットを返します。

関数メソッド

任意の総称関数のメソッドからなる配列は methods で取得できます。メソッドのディスパッチで使われるテーブルから指定した型を受け取るメソッドを検索するには methodswith を使います。

マクロの展開とコードの低水準化

メタプログラミングの章でも説明したように、macroexpand 関数は指定したマクロを展開した式をアンクオートした形 (Expr) で返します。macroexpand を使うときは式を quote する必要があります (そうしないとマクロの評価結果が関数に渡されてしまいます!)。例えば次のようにします:

julia> macroexpand(@__MODULE__, :(@edit println("")) )
:(InteractiveUtils.edit(println, (Base.typesof)("")))

任意の式 (Expr 型の値) は関数 Base.Meta.show_sexpr を使うと S 式風に表示でき、dump を使うとネストされた詳細な形で式を表示できます。

最後に、Meta.lower 関数は任意の式の低水準形式 (lowered form) を返します。これは言語の様々な構文が基礎的な操作 (代入・条件分岐・関数呼び出しなど) にどう対応付くのかを理解するために特に重要です:

julia> Meta.lower(@__MODULE__, :( [1+2, sin(0.5)] ))
:($(Expr(:thunk, CodeInfo(
    @ none within `top-level scope'
1 ─ %1 = 1 + 2
│   %2 = sin(0.5)
│   %3 = Base.vect(%1, %2)
└──      return %3
))))

中間表現とコンパイル後の表現

総称関数が異なる型シグネチャを持つ複数のメソッドを持てるために、関数の低水準形式を調べるときは特定のメソッドを選択する必要があります。メソッドごとの低水準コードは code_lowered で、型推論が行われた形式は code_typed で確認できます。code_warntypecode_typed の出力をハイライトした状態で表示します。

さらにマシンに使づくと、code_llvm で関数の LLVM 中間表現を、code_native で最終的にコンパイルされる機械語を確認できます (code_native を呼ぶと、これまでに呼ばれていない全ての関数に対する JIT コンパイルとコード生成が行われます)。

利便性のため、上述の関数に通常の関数呼び出しを渡せるマクロバージョンが存在します。これらのマクロは引数を評価してその型を自動的に展開します:

julia> @code_llvm +(1,1)

define i64 @"julia_+_130862"(i64, i64) {
top:
    %2 = add i64 %1, %0
    ret i64 %2
}

詳細は @code_lowered, @code_typed, @code_warntype, @code_llvm, @code_native を参照してください。

デバッグ情報の出力

これまでに触れた関数とマクロは出力されるデバッグ情報の量を制御するためのキーワード引数 debuginfo を受け取ります:

julia> @code_typed debuginfo=:source +(1,1)
CodeInfo(
    @ int.jl:53 within `+'
1 ─ %1 = Base.add_int(x, y)::Int64
└──      return %1
) => Int64

debuginfo に渡せる値は :none, :source, :default です。デフォルトではデバッグ情報は出力されませんが、Base.IRShow.default_debuginfo[] = :source とすればデフォルトの設定を変更できます。

広告