Juliaのgetpropertyが面白い

Juliaの標準パッケージの中身を見ていると実装がなかなか面白くて勉強になる.今回getpropertyという面白いものを知ったので共有します.

Base.getpropertyとは

[構造体].[フィールド名]と実行したら呼ばれる関数で,デフォルトでは構造体のインスタンスのフィールドの値が戻ってきます.

struct Hoge
    x
end

h = Hoge(3)

xには3が入っているのでこのように動作しますね.

julia> h.x
3

では存在しないフィールド名を使うとどうなるでしょうか

julia> h.type
ERROR: type Hoge has no field type
Stacktrace:
 [1] getproperty(::Any, ::Symbol) at ./sysimg.jl:18

当然エラーがでますね.しかしエラーに注目してください,getpropertyという関数が呼ばれています.第一引数が構造体のインスタンスでこの例の場合hです.第二引数がSymbolでこの例の場合typeです.

これを定義することでJuliaの多重ディスパッチにより定義したgetpropertyが実行されて存在しないフィールドにアクセスできるかのような動作を実現できます.

Base.getpropertyを定義する

存在するh.xxの値を返して,h.typehの型名を返してみる.なお,typeof関数があるのでこの実装には無駄ですが一例としてこのような実装をしてみます.

struct Hoge
    x
end

function Base.getproperty(H::Hoge, d::Symbol)
    # H.typeのときはHの型であるHogeを返す
    d == :type && return typeof(H)
    #  その他の場合そのままフィールドの値を返す
    return getfield(H, d)
end

julia>  h = Hoge(3)
Hoge(3)

julia> h.x
3

julia> h.type
Hoge

こんな感じに動作します.supertypeが同じ複数のstructを定義して似たような動作をさせたいけどそれぞれのフィールド変数が大きく異なる場合などに便利かもしれない.

Julia REPLで候補としてでてくるようにする

このままではREPLでTAB補間しようとしたときにHogeのフィールドネームとしてtypeが現れません.

現れるようにするのも簡単で,Base.propertynamesを定義すればOK.

function Base.propertynames(H::Hoge, private::Bool=false)
    return (:type, fieldnames(Hoge)...)
end

TAB補間しようとするとtypeも候補として出てくる.

julia> h.
type x