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)

無名関数式は実際のコードでも多く利用されます。

リファレンス

[email protected] ── マクロ

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

[email protected] ── マクロ

@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])

[email protected] ── マクロ

@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]

[email protected] ── マクロ

@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

[email protected] ── マクロ

@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])

[email protected] ── マクロ

@ntuple N expr

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

[email protected] ── マクロ

@nall N expr

無名関数式 expr が生成する全ての式が true に評価されるかどうかを判定します。

@nall 3 d->(i_d > 1) とすれば (i_1 > 1 && i_2 > 1 && i_3 > 1) という式が生成されます。このマクロは境界検査に有用です。

[email protected] ── マクロ

@nany N expr

無名関数式 expr が生成する式のいずれかが true に評価されるかどうかを判定します。

@nany 3 d->(i_d > 1) とすれば式 (i_1 > 1 || i_2 > 1 || i_3 > 1) が生成されます。

[email protected] ── マクロ

@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
日本語 Julia 書籍 (Amazon アソシエイト)
1 から始める Julia プログラミング
Julia プログラミングクックブック―言語仕様からデータ分析、機械学習、数値計算まで
スタンフォード ベクトル・行列からはじめる最適化数学