= :foo # シンボル :foo を作成
x = Symbol("foo") # シンボル :foo を作成
y
println(x == y) # true
true
司馬 博文
1/23/2022
Symbol
型とExpr
型,そしてExpr
型からExpr
型への関数であるマクロを用いたメタプログラミングについて解説する.
A Blog Entry on Bayesian Computation by an Applied Mathematician
$$ %%% 汎用コード列%%% 演算子
%%% 線型代数学%%% 複素解析学 %%% 集合と位相
%%% 形式言語理論 %%% Graph Theory
%%% 多様体 %%% 代数 %%% 代数的位相幾何学 %%% 微分幾何学 %%% 函数解析 %%% 積分論%%% Fourier解析 %%% 数値解析
%%% 確率論%%% 情報理論 %%% 量子論 %%% 最適化 %%% 数理ファイナンス
%%% 偏微分方程式 %%% 常微分方程式 %%% 統計力学 %%% 解析力学
%%% 統計的因果推論 %%% 応用統計学 %%% 数理統計%%% 計量経済学
%%% 無限次元統計模型の理論%%% Banach Lattices
%%% 圏 %代数の圏 %Metric space & Contraction maps %確率空間とMarkov核の圏 %Sober space & continuous map %Category of open subsets %Category of sheave %Category of presheave, PSh(C)=[C^op,set]のこと %Convergence spaceの圏 %一様空間と一様連続写像の圏 %フレームとフレームの射 %その反対圏 %滑らかな多様体の圏 %Quiverの圏
%%% SMC %%% 括弧類%%% 予約語
%%% 略記
%%% 矢印類 $$
プログラムにプログラミングをさせること.
これはプログラミング言語を抽象構文木として捉えることで,一切の「プログラムの実行」に関連した意味論を排除することができることによって可能となる.
他の「プログラムの実行」関連の処理以前に,parse とセットでマクロの展開が行われる.まさに,「プログラミングのプログラミング」である.
Symbol
型:処理系内部の名前identifier
とも呼べる.
2通りのコンストラクタがある.
foo
という変数がソースコード内で使用されている時,処理系内ではfoo
という名前のシンボルが作成され,それらのテーブルを保持する.
Expr
:AST が型を持って Julia で操作可能なオブジェクトとして登場!構文論における「文 expression
」とは,木である.これを AST (Abstract Syntax Tree) ともいう.
dump
:()
による引用 (quoting),または quote
-end
ブロックの2通りがある.
Expr
head: Symbol call
args: Array{Any}((3,))
1: Symbol +
2: Int64 2
3: Int64 3
code を parse までして実行はせず,抽象構文木 object として保持する.処理系に parse された後の抽象構文木も object として扱えるのが Julia!
eval
関数parse 以降の実行工程を,現在の Module でテーブルを作って 実行する.
必ずその Module の global テーブルで実行される.関数定義のなかで eval されていても,global table でなされる.
2つの field head
と args
の繰り返しからなる.head
は構文木の種類,args
が各要素.
$( )
Hello, Julia!
この時ex
がSymbol
型オブジェクトでも,剥がされて,String
型として補間される.1
# 評価してから補間
x = 2
y = 3
result = "The sum of $x and $y is $(x + y)."
println(result) # 出力: The sum of 2 and 3 is 5.
The sum of 2 and 3 is 5.
Symbol
型を保持したい場合は,さらにex
をQuoteNote
でクオートする:
「マクロの展開」という言い方をする.
マクロの展開は,parse のすぐ次の段階で行われるので,一番速い.これが,高級言語か…….
マクロの引数は,Shell command のようにスペースで区切って与える.
一方,関数のように @macro(x,y,...)
と与えることもできるが,慣習に逆らうという.
この2つは構文解析のされ方が違う.
REPLで主に使われる,Shell commandに似てる.
構文木に特殊な情報を差し込むことで,最適化が進む.
assertは部分的にそうであったが,String型のみを受け取るマクロのことを特に「非標準文字列リテラル」と呼ぶ.
マクロ名は_strで終わり,文字列の前にマクロ名から_strを除いたものを接続しても呼び出せる.これは冠頭演算子っぽい,いや,タグっぽいかもしれない.
殆どが「特殊なリテラル」として使われるために,こう呼ぶ.従って,リテラルを持つ特殊な型のconstructorだと思えばいい.
マクロとは,Expr
上の,Expr
を返す関数であり,keyword が macro
になるだけで,関数定義と同じ.
macro sayhello(name)
return :(println("Hello, ", $name))
end
@sayhello("world") # 展開されるコードは println("Hello, world")
Hello, world
$(arg1)
が頻出することになる.識別子の変換規則 引数ex::ExprもJuliaコードであるから,マクロ定義内の文章と衝突することがあり得る,メタプログラミング故の悩みの種である.
識別子の変換が,マクロが定義されたmodule内の大域テーブルでなされるのが原則だが,次は例外である. 1. global宣言なしで代入された時 2. local宣言がある時 3. 関数定義の引数である時
これら3条件を満たすためにローカル変数と解釈された識別子は,マクロ展開時に新しい変数に置き換えられる(#10#nameなど).これはマクロ呼び出し側にある別の識別子との衝突を避けるためである.このマクロ展開の仕方を【hygene macro】という.
まとめ マクロが返す構文木やリテラルに含まれる識別子は,次のいずれかの経路を辿ったものである. 1. esc関数でエスケープされていれば,識別子は変換されずそのまま維持される. 2. 代入,local宣言,関数引数のいずれかであれば,新しいローカル変数が生成される. 3. いずれでもない場合は,マクロを定義したmoduleのglobal変数に変換される.
メタプログラミングの例
Symbol型の即値と解さない方をデフォルトとした方が利便性が高いからである.↩︎