元バイオ系

元バイオウェット系がデータサイエンスやらを勉強していくブログ。 基本自分用のまとめ。

Julia tips #10: マクロを使って動的に構造体を定義する

Juliaでゼロから学ぶDeep Learningという記事を書きました。

hotoke-x.hatenablog.com

実はこちらの実装、ほぼ本家のPythonそっくりになるように書かれています。

というのも私がDeep Learning初心者なので、自己流で進めすぎるとどこが間違っているのかわからなくなってしまうためです。

「Juliaらしさ」も未だつかめておらず、見比べたらわかるんですがほぼPythonです。

実装に四苦八苦する中でマクロについてちょっと調べる機会があったので記事にしておこうと思った次第です。

ただし、Documentを全て読んだわけではないですし、今でも色々わかっていないということはご了承下さい。

(忘れないうちに記事にして消化しておきたかった)

構造体を動的に定義したい

とりあえず何も考えずに以下のコードを実行(ver. 1.1.0です)。

macro make_struct(struct_name, schema...)
    fields=[:($(entry.args[1])::$(entry.args[2])) for entry in schema]
    field_names=[:($(entry.args[1])) for entry in schema[1:2]]
    obj = :OBJ
    __init = [:($obj.$name=$name) for name in field_names] 
    esc(quote mutable struct $struct_name
        $(fields...)
            function (::Type{$(struct_name)})($(field_names...))
                $obj = new()
                $(__init...)
                $obj
            end
        end
    end)
end
println(@macroexpand @make_struct STRUCT_NAME (x,Integer) (y, Integer) (z, Integer))
@make_struct STRUCT_NAME (x, Integer) (y, Integer) (z, Integer)
STRUCT_NAME(1,2)

STRUCT_NAMEという構造体が定義されたはずです。

このマクロは前半でコードの部品生成、後半部分で部品の埋め込みを行っています。

部品生成部分にコメントをつけました。

macro make_struct(struct_name, schema...)
    # 部品(Expr)生成###################################
    fields=[:($(entry.args[1])::$(entry.args[2])) for entry in schema]   # [x::Integer, y::Integer, z::Integer]を生成
    field_names=[:($(entry.args[1])) for entry in schema[1:2]]   # [x::Integer, y::Integer]を生成(内部コンストラクタの引数に使う)
    obj = :OBJ   # このOBJという名前を内部コンストラクタで使う
    __init = [:($obj.$name=$name) for name in field_names]   # [OBJ.x=x, OBJ.y=y]を生成
    # 部品(Expr)生成###################################

    esc(quote mutable struct $struct_name
        $(fields...)
            function (::Type{$(struct_name)})($(field_names...))
                $obj = new()
                $(__init...)
                $obj
            end
        end
    end)
end
println(@macroexpand @make_struct STRUCT_NAME (x,Integer) (y, Integer) (z, Integer))
@make_struct STRUCT_NAME (x, Integer) (y, Integer) (z, Integer)
STRUCT_NAME(1,2)

埋め込み部分が何をしているかは

println(@macroexpand @make_struct STRUCT_NAME (x,Integer) (y, Integer) (z, Integer))

でわかります。

おわりに

ゼロから始めるDeep Learningを実装する中でJuliaの辞書が遅いということに気がついたので、「動的にstructを定義できたら速いのでは?」と思ったので今回のようなことを思いついたのでした。

ちなみに、「Juliaの辞書が遅い」というのは正確には間違っています。

Juliaの辞書は型指定無しもしくは抽象型で作った時にPythonより遅くなるようです。具象型であればPythonより高速です。

これは後ほど記事にする予定です。