リフレクションとイントロスぺクション
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
オブジェクトに含まれる各フィールドの型は変数 Point
の types
フィールドに格納されます:
julia> Point.types
svec(Int64, Any)
Point
型の定義で x
は Int
という型注釈が付いていますが、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_warntype
は code_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
とすればデフォルトの設定を変更できます。