Base.Cartesian モジュール
Cartesian モジュールは多次元アルゴリズムを書くときに役立つマクロを提供します (このモジュールはエクスポートされません)。多次元アルゴリズムであっても簡単なテクニックを使えばたいていは書くことができますが、Base.Cartesian
が活用できる/必要になる場面はいくつか存在します。
基本的な使い方
簡単な使用例を示します:
@nloops 3 i A begin
s += @nref 3 A i
end
これは次のコードを生成します:
for i_3 = axes(A, 3)
for i_2 = axes(A, 2)
for i_1 = axes(A, 1)
s += A[i_1, i_2, i_3]
end
end
end
一般的に言うと、Cartesian モジュールは反復される部分を持つ汎用なコードを書くためにあります。この例で言えばループが入れ子になって反復されています。他の応用例としては反復された式 (例えばループ展開) の記述や可変個の引数を持った関数の "splat" 構文 (i...
) を使わない呼び出しがあります。
基本的な構文
@nloops
の (基本的な) 構文は次の通りです:
- 第一引数は (変数でない) 整数であり、ループの個数を指定します。
- 第二引数は反復変数の先頭に付くシンボルです。上記の例では
i
としているので、反復変数としてi_1
,i_2
,i_3
が生成されます。 - 第三引数は各反復変数の区間を指定します。ここで変数 (シンボル) を使うと
axes(A, dim)
と解釈されます。 - 最後の引数はループの本体です。上記の例では
begin...end
となっています。
@nloops
にはこれ以外の機能もあります。詳しくはリファレンスを参照してください。
@nref
は同様のパターンに従って添え字アクセス式を生成します。例えば @nref 3 A i
は A[i_1,i_2,_i_3]
となります。この二つのマクロの引数は左から右に読むのが一般的な慣習であり、@nloops
では for i_2 = axes(A, 2)
に合わせて @nloops 3 i A expr
となり、@nref
では A[i_1,i_2,i_3]
に合わせて @nref A i
となります。
Cartesian
モジュールを使ってコードを書くときは、@macroexpand
で生成されるコードを確認するとデバッグが楽に行えるでしょう:
julia> @macroexpand @nref 2 A i
:(A[i_1, i_2])
式の個数を指定する
@nloops
と @nref
はどちらも第一引数に式の個数を指定します。この値は整数である必要がありますが、多次元に対応した関数を書くときは式の個数をハードコードできない場合もあるでしょう。そのような場合には @generated function
を付けて被生成関数を使うことが推奨されます:
@generated function mysum(A::Array{T,N}) where {T,N}
quote
s = zero(T)
@nloops $N i A begin
s += @nref $N A i
end
s
end
end
もちろん、quote
ブロックの前に式の準備や計算を行えます。
マクロの引数に無名関数式を使う
Cartesian
の中でおそらく最も便利な機能が、パース時に評価される無名関数式を使った式の生成です。簡単な例を示します:
@nexprs 2 j->(i_j = 1)
@nexprs
は同じパターンに従う n
個の式を生成します。このコードであれば次の文が生成されます:
i_1 = 1
i_2 = 1
生成される文では、無名関数が返す式に含まれる "孤立した" j
(無名関数の変数) が区間 1:2
に含まれる値で置き換わります。一般に Cartesian
は LaTeX 風の構文を使います。このマクロを使うと添え字 j
を使った計算が行えます。配列の歩長を計算する例を示します:
s_1 = 1
@nexprs 3 j->(s_{j+1} = s_j * size(A, j))
これは次の式を生成します:
s_1 = 1
s_2 = s_1 * size(A, 1)
s_3 = s_2 * size(A, 2)
s_4 = s_3 * size(A, 3)
無名関数式は実際のコードでも多く利用されます。
リファレンス
Base.Cartesian.@nloops
── マクロ
@nloops N itersym rangeexpr bodyexpr
@nloops N itersym rangeexpr preexpr bodyexpr
@nloops N itersym rangeexpr preexpr postexpr bodyexpr
N
個のネストされたループを生成します。itersym
が反復変数の接頭辞となります。rangeexpr
は無名関数式もしくは単純なシンボル var
である必要があります。シンボル var
を指定すると、d
個目のループの区間は d
番目の次元に対するサイズ axes(var, d)
となります。
省略可能な引数 preexpr
と postexpr
はそれぞれループ本体の前と後に実行される式です。例えば
@nloops 2 i A d -> j_d = min(i_d, 5) begin
s += @nref 2 A j
end
は、次の式を生成します:
for i_2 = axes(A, 2)
j_2 = min(i_2, 5)
for i_1 = axes(A, 1)
j_1 = min(i_1, 5)
s += A[j_1, j_2]
end
end
postexpr
だけを指定したいときは preexpr
に nothing
を指定してください。括弧とセミコロンを使えば複数の文を含む式を与えることもできます。
Base.Cartesian.@nref
── マクロ
@nref N A indexexpr
A[i_1, i_2, ...]
のような式を生成します。indexexpr
は反復中の添え字に付いている接頭辞を表すシンボル、もしくは無名関数式です。
例
julia> @macroexpand Base.Cartesian.@nref 3 A i
:(A[i_1, i_2, i_3])
Base.Cartesian.@nextract
── マクロ
@nextract N esym isym
N
個の変数 esym_1
, esym_2
, ..., esym_N
を isym
が生成する値で初期化します。isym
は Symbol
または無名関数式です。
例えば @nextract 2 x y
は次の式を生成します:
x_1 = y[1]
x_2 = y[2]
また @nextract 3 x d->y[2d-1]
は次の式を生成します:
x_1 = y[1]
x_2 = y[3]
x_3 = y[5]
Base.Cartesian.@nexprs
── マクロ
@nexprs N expr
無名関数式 expr
を使って N
個の式を生成します。
例
julia> @macroexpand Base.Cartesian.@nexprs 4 i -> y[i] = A[i+j]
quote
y[1] = A[1 + j]
y[2] = A[2 + j]
y[3] = A[3 + j]
y[4] = A[4 + j]
end
Base.Cartesian.@ncall
── マクロ
@ncall N f sym...
関数呼び出し式を生成します。sym
は任意個数の関数の引数を表します。最後の式には無名関数式を指定でき、その場合は無名関数式を使って N
個の引数が生成されます。
例えば @ncall 3 func a
は次の式を生成します:
func(a_1, a_2, a_3)
また @ncall 2 func a b i->c[i]
は次の式を生成します:
func(a, b, c[1], c[2])
Base.Cartesian.@ntuple
── マクロ
Base.Cartesian.@nall
── マクロ
@nall N expr
無名関数式 expr
が生成する全ての式が true
に評価されるかどうかを判定します。
@nall 3 d->(i_d > 1)
とすれば (i_1 > 1 && i_2 > 1 && i_3 > 1)
という式が生成されます。このマクロは境界検査に有用です。
Base.Cartesian.@nany
── マクロ
@nany N expr
無名関数式 expr
が生成する式のいずれかが true
に評価されるかどうかを判定します。
@nany 3 d->(i_d > 1)
とすれば式 (i_1 > 1 || i_2 > 1 || i_3 > 1)
が生成されます。
Base.Cartesian.@nif
── マクロ
@nif N conditionexpr expr
@nif N conditionexpr expr elseexpr
if ... elseif ... else ... end
が並んだ式を生成します。例えば
@nif(3,
d->(i_d >= size(A,d)),
d->(error("Dimension ", d, " too big")),
d->println("All OK"))
とすると、次の式が生成されます:
if i_1 > size(A, 1)
error("Dimension ", 1, " too big")
elseif i_2 > size(A, 2)
error("Dimension ", 2, " too big")
else
println("All OK")
end