Julia で定義済の変数を"名前 (`Symbol`)"で取得する方法

## 初めに

Julia でメタプログラミングやってると「なんで `isdefined()` は存在するのに名前指定で変数を取得する関数がないのだ？」って思いますよね？[^metaprogramming]

ということでそういう関数を書いてみました。

[^metaprogramming]: まぁそもそもやってる人少ないとは思いますが(^-^;

【2017/08/16】もっとシンプルな方法があったので大幅に加筆修正しました＞＜

## 先に成果物

こんな関数作りました（対象バージョン：Julia v0.5.2/v0.6.0）：

```jl:getbyname.jl
@inline getbyname(name::Symbol) = getfield(current_module(), name)
@inline getbyname(o, name::Symbol) = getfield(o, name)
@inline Base.getindex(m::Module, name::Symbol) = getbyname(m, name)
```

主な使い方：

+ `getbyname(«symbol»)` で、引数に渡した名前（`Symbol`）の変数・定数・関数・型等が存在したらその実体（値）を返す（なければ `UndefVarError: «symbol» not defined` のようなエラーになります）。
    + 例えば `getbyname(:Int) # => Int64`、`getbyname(:π) # => π = 3.1415926535897...` など
+ `getbyname(«module», «symbol»)` で、そのモジュールで定義されている（または `import` している等の理由で参照可能な）変数・定数・関数・型等を（存在すれば）を返す。
    + `Base[:exp]` のように `Dictionary` のキーを指定して値を返すのと同じ記法で書けるエイリアスも用意（この場合返ってくるのは `exp()` 関数ですね）。
+ `getbyname(«object», «symbol»)` で、そのオブジェクトに存在するメンバを返す。

要するに `isdefined([△,] ○)` で true が返ってくるものは `getbyname([△,] ○)` でその実体が取得できる、ということ[^idefined_index]。

具体例：

```jl:getbyname_sample.jl
include("getbyname.jl")

@show methods(getbyname)
# => # 2 methods for generic function "getbyname":
# getbyname(name::Symbol) at …
# getbyname(o, name::Symbol) at …

@show isdefined(:Int)
# => true
@show getbyname(:Int)
# => Int64

@show isdefined(:undef_var)
# => false
try
    @show getbyname(:undef_var)
    # Nothing to be shown.
catch ex
    @show ex
    # => ex = UndefVarError(:undef_var)
end

@show getbyname(big"1", :d)
# => Ptr{UInt64} @0x00007fd8d3235900

@show [getbyname(name) for name=names(current_module()) if ismatch(r"Main", string(name))]
# => Module[Main]

@show [Base[name] for name=names(Base) if ismatch(r"[Nn]ame", string(name))]
# => Function[basename, dirname, fieldname, fieldnames, fullname, gethostname, getsockname, module_name, names, tempname]
```

[^idefined_index]: 実際には `isdefined()` には第2引数に `Int` つまりインデックス番号を受け取る使い方もあるのですが、`getbyname()` はそれには対応していません。それは「名前」じゃないですしそれで `true` が返ってくるようなものなら普通に `△[○]` でその値が取得できるように実装されているはずなので。

### 【追記】解説

はい、新しい関数定義するまでもありませんでした＞＜
[`getfield()`](https://docs.julialang.org/en/stable/stdlib/base/#Core.getfield) という関数がありました。
ドキュメントには「（複合）型の値の、名前で指定されたフィールドを抽出する」というような説明しかありませんが、第1引数がオブジェクトだけでなくモジュールでも使えて、その場合そのモジュールに定義されている（もしくはとにかく `M.v` の形式（`M`はモジュール名）で参照できる）変数・関数・型等を取得できます。
というよりドキュメントの後半の説明がすべてですね。「`a.b` という文法は `getfield(a, :b)`を呼んでいます」もうこれに尽きる。

ただ、`isdefined(«symbol»)` という引数1個に対応する直接的なものはない（対応するのは `getfield(current_module(), «symbol»）` し、関数名も「フィールドを取得する」なので、「（モジュールに定義された）変数等を『名前』で取得する」という別関数（というかほぼエイリアス）として用意することは決して無意味ではないのではないか、と思います（と自分に言い聞かせています＞＜）。

【追記ここまで】

### 使い途

これだけ見ると「これ何に使うの？」「名前が分かっていれば初めから `Int` のように書けば良いし、存在するかどうかは `isdefined(:name)` で確認できるし、意味あるの？」等の疑問が湧くかもしれません。
なので、「なぜ私がこんなもの書いたのか」という理由・動機の説明を兼ねて。
主な「使い途」は以下の2つ。

1. 動的に変数等を参照したい
2. 普通には参照できない変数等を参照したい

#### 1. 動的に変数等を参照したい

元々の動機というのがこれ。
例えば先の具体例コードの最後の2つ。`names(m::Module)` 関数は、そのモジュールに定義されている変数（関数・型 等）を名前（`Symbol`）の一覧で出す関数。
それがあるのに、その名前を指定してその変数（関数・型 等）の実体を返す関数が、ドキュメントのどこを見ても見当たらない。それって対称性がないというか、片手落ちというか。

ま、結果的には「`getfield()` 関数を使えばその要求を満たすコトが出来る」ということは分かったのですが、ドキュメントを隅から隅まで見ないと気付かない（見ていても気付かない）なんて、…。

【一部修正後のコードに合わせて内容修正】

#### 2. 普通には参照できない変数等を参照したい

以下のコードを見てください：

```jlcon
julia> new_var = gensym(:v)
Symbol("##v#274")

julia> eval(:($new_var = 123))
123

julia> getbyname(new_var)
123

```

まず最初に `new_var = gensym(:v)` で新しい変数名（`Symbol`）を生成しています。`gensym()` という関数は、今までに生成されているどの `Symbol` ともかぶらない一意な新しい `Symbol` を自動生成する関数です。マクロでコードを自動生成する場合などによく利用します。
で、その新しい変数に値を代入するには、`quote` されたコード（コードブロック）中で `$new_var` のように変数名を参照して式を記述し、それを `eval()` します。REPL で簡単に書いたのが↑のコードの2つめの式。これで `new_var`（が保持する `Symbol`） を名前に持つ変数ができました。
この変数に、どうやってアクセスすれば良いのでしょう？
自動生成された名前は `##v#274` のようなもの[^auto_name]になっており、これをそのままコード中に書くと `#` がコメントの始まりと解釈されて参照できません。

[^auto_name]: もちろん実行する度に結果（主に最後の整数値（＝実はカウンタ値））は異なります。

まぁ普通はこういう自動生成変数はそのマクロ内（等）で完結する用途でしか使わず、後で外から参照することはほぼない（というかないような設計にすべき）なのですが、手段としては用意しておくのは悪くないのではないかな、と。

## 【追記・修正】修正前のコードとの比較

記事修正する前の成果物として、以下のようなコードをあげていました（対象バージョン：v0.6.0）。

```jl:getbyname_20170813.jl
@inline getbyname(name::Symbol) = getbyname(current_module(), name)
@inline getbyname(m::Module, name::Symbol) = eval(m, name)
@inline Base.getindex(m::Module, name::Symbol) = getbyname(m, name)

getbyname(o, name::Symbol) = _getbyname(o, Val{name})

# for VERSION < v"0.6-": @generated function _getbyname{T}(o, ::Type{Val{T}})
@generated function _getbyname(o, ::Type{Val{T}}) where T
    :(o.$T)
end
```

コメント中にあるように、`@generated function _getbyname(o, ::Type{Val{T}}) where T` の行を `@generated function _getbyname{T}(o, ::Type{Val{T}})` に書き換えれば v0.5.x 以前でも動作します（v0.5.2 で動作確認済）。

こちらの `getbyname()` のコード例にもあるように `eval(m, name)` とすれば、「そのモジュールにあるその名前（`Symbol`）の変数（等）」を取得できます。

でも `eval()` って、やっぱ動的すぎて危険ですよね。（第2）引数が `Symbol` であれば、ASTの解釈と言ってもたった一つの識別子（変数）の解釈だけなので安全と言えば安全ですが、できればあまり表だって使いたくない。

なので、関数化した、というわけ。`eval()` を使うコードはその小さな関数の中だけにして、しかも引数は型アノテーションを付けて `Module` と `Symbol` しか受け付けないようにしています。これで最低限の安全は保証できる、と[^eval_vs_generated_fn]。

[^eval_vs_generated_fn]: さらに安全にするために、毎回 `eval()` するのではなく、第1引数が `Module` じゃない場合と同様、`@generated function` にする（というか `_getbyname(o, ::Typa{Val{T}})` をそのまま利用）という方法もあったのですが、毎回 `eval()` した方が圧倒的にパフォーマンス良かった（10倍くらい速かった）ので~~現在の形になりました~~この形にしていました…が `getfield()` というもっと良い方法が見つかった今となっては…。

あとコードを見ていただくと分かる通り、（モジュール内の）変数を取得するコードは `eval()` をそのまま利用していますが、オブジェクト（構造体）のメンバを参照する方は、`@generated function` という仕組みを利用しています。
これはパフォーマンス向上目的です。

オブジェクト（構造体）のメンバ参照も、`eval()` を使って `eval(:(o.$name))` のように書くことも出来ます。ただこれ、`eval(name)` に比べるととても遅いのです。
理由は、これは単純な `Symbol` の解釈ではなく、まず「`o` という変数の `name`（という変数に保持された `Symbol` 値）という名前を持つメンバを参照する」というASTが作られて、それを `eval()` で評価する、という流れになっているからです。言ってみれば、「毎回動的にソースコードが生成されてその都度コンパイルが走ってその上で実行される」、ということ。
それに比べて `@generated function` と言う仕組みは、`eval()` と似たような仕組みなのですが、その評価が行われるのは最初の1回だけ。2回目以降にその関数が呼ばれたときには、そのASTを解釈済のコードが実行されます。先ほどの例との対比で言うと「最初だけソースコードが生成されコンパイルされて実行される、2回目以降はそのコンパイル済のコードが実行される（だから速い）」ということ。
ただしそこに `Symbol` を外部からパラメータとして渡すのに一工夫要ります。値型（`Val{name}` の部分）という仕組みを利用しています。その辺の詳細は、Julia の公式ドキュメントを眺めてみてください。

ただ、これらの方法より、`getfield()` を利用した方がさらにもっとパフォーマンスは良いです。
`getfield()` は、C言語レベルで実装されている [組込関数](https://docs.julialang.org/en/stable/devdocs/functions/#Builtins-1)。要するに julia のかなりコアな部分の関数で[^core_getfield]、その分パフォーマンスは十分考慮されている、ということですね。

[^core_getfield]: 実際（前にもちらっと言及しましたが）、Julia のコード `a.b` は、評価時に `getfield(a, :b)` に一旦置き換えられてからJITコンパイルが走る仕組みになっているようです。

## 参考

+ [`Core.getfield` - Function - Essentials · The Julia Language](https://docs.julialang.org/en/stable/stdlib/base/#Core.getfield)
+ [Metaprogramming · The Julia Language](https://docs.julialang.org/en/stable/manual/metaprogramming/)
+ ["Value types" - Types · The Julia Language](https://docs.julialang.org/en/stable/manual/types/#"Value-types"-1)