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 の (基本的な) 構文は次の通りです:

@nloops にはこれ以外の機能もあります。詳しくはリファレンスを参照してください。

@nref は同様のパターンに従って添え字アクセス式を生成します。例えば @nref 3 A iA[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) となります。

省略可能な引数 preexprpostexpr はそれぞれループ本体の前と後に実行される式です。例えば

@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 だけを指定したいときは preexprnothing を指定してください。括弧とセミコロンを使えば複数の文を含む式を与えることもできます。

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_Nisym が生成する値で初期化します。isymSymbol または無名関数式です。

例えば @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 ── マクロ

@ntuple N expr

N 要素のタプルを生成します。@ntuple 2 i(i_1, i_2) を生成し、@ntuple 2 k->k+1(2,3) を生成します。

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
広告