Base.Cartesian

该(未导出的)Cartesian模块提供了宏,便于编写多维算法。通常情况下,您可以使用 straightforward techniques 来编写此类算法;然而,在某些情况下,Base.Cartesian 仍然是有用或必要的。

Principles of usage

一个简单的用法示例是:

@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...)的情况下创建具有可变参数数量的函数调用。

Basic syntax

@nloops 的(基本)语法如下:

  • 第一个参数必须是一个整数(不是变量),指定循环的次数。
  • 第二个参数是用于迭代器变量的符号前缀。这里我们使用了 i,并生成了变量 i_1, i_2, i_3
  • 第三个参数指定了每个迭代器变量的范围。如果在这里使用一个变量(符号),它将被视为 axes(A, dim)。更灵活地,你可以使用下面描述的匿名函数表达式语法。
  • 最后一个参数是循环的主体。在这里,这就是出现在 begin...end 之间的内容。

@nloops 的一些附加功能在 reference section 中进行了描述。

@nref 遵循类似的模式,从 @nref 3 A i 生成 A[i_1,i_2,i_3]。一般的做法是从左到右读取,这就是为什么 @nloops@nloops 3 i A expr(如 for i_2 = axes(A, 2),其中 i_2 在左边,范围在右边),而 @nref@nref 3 A i(如 A[i_1,i_2,i_3],其中数组在前)。

如果您正在使用 Cartesian 开发代码,您可能会发现通过使用 @macroexpand 检查生成的代码可以更容易地进行调试:

julia> @macroexpand @nref 2 A i
:(A[i_1, i_2])

Supplying the number of expressions

这两个宏的第一个参数是表达式的数量,必须是一个整数。当你编写一个打算在多个维度中工作的函数时,这可能不是你想要硬编码的内容。推荐的方法是使用 @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 块之前准备表达式或进行计算。

Anonymous-function expressions as macro arguments

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)

匿名函数表达式在实践中有许多用途。

Macro reference

Base.Cartesian.@nloopsMacro
@nloops N itersym rangeexpr bodyexpr
@nloops N itersym rangeexpr preexpr bodyexpr
@nloops N itersym rangeexpr preexpr postexpr bodyexpr

生成 N 个嵌套循环,使用 itersym 作为迭代变量的前缀。rangeexpr 可以是一个匿名函数表达式,或者是一个简单的符号 var,在这种情况下,范围为 axes(var, d),其中 d 是维度。

可选地,您可以提供 "pre" 和 "post" 表达式。这些表达式分别在每个循环的主体中首先和最后执行。例如:

@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

如果您只想要一个后表达式,请为前表达式提供 nothing。使用括号和分号,您可以提供多语句表达式。

source
Base.Cartesian.@nrefMacro
@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])
source
Base.Cartesian.@nextractMacro
@nextract N esym isym

生成 N 个变量 esym_1esym_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]
source
Base.Cartesian.@nexprsMacro
@nexprs N expr

生成 N 个表达式。expr 应该是一个匿名函数表达式。

示例

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
source
Base.Cartesian.@ncallMacro
@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])
source
Base.Cartesian.@ncallkwMacro
@ncallkw N f kw sym...

生成一个带有关键字参数 kw... 的函数调用表达式。与 @ncall 的情况一样,sym 代表任意数量的函数参数,最后一个参数可以是匿名函数表达式,并扩展为 N 个参数。

示例

julia> using Base.Cartesian

julia> f(x...; a, b = 1, c = 2, d = 3) = +(x..., a, b, c, d);

julia> x_1, x_2 = (-1, -2); b = 0; kw = (c = 0, d = 0);

julia> @ncallkw 2 f (; a = 0, b, kw...) x
-3
source
Base.Cartesian.@ntupleMacro
@ntuple N expr

生成一个 N-元组。 @ntuple 2 i 将生成 (i_1, i_2),而 @ntuple 2 k->k+1 将生成 (2,3)

source
Base.Cartesian.@nallMacro
@nall N expr

检查由匿名函数表达式 expr 生成的所有表达式是否都评估为 true

@nall 3 d->(i_d > 1) 将生成表达式 (i_1 > 1 && i_2 > 1 && i_3 > 1)。这对于边界检查非常方便。

source
Base.Cartesian.@nanyMacro
@nany N expr

检查由匿名函数表达式 expr 生成的任何表达式是否评估为 true

@nany 3 d->(i_d > 1) 将生成表达式 (i_1 > 1 || i_2 > 1 || i_3 > 1)

source
Base.Cartesian.@nifMacro
@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
source