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
.
В reference section описаны некоторые дополнительные функции @nloops
.
@nref
следует аналогичному шаблону, генерируя A[i_1,i_2,i_3]
из @nref 3 A i
. Общая практика заключается в том, чтобы читать слева направо, именно поэтому @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.@nloops
— Macro@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
.
При желании вы можете предоставить "предварительные" и "последующие" выражения. Эти выражения выполняются сначала и в последнюю очередь, соответственно, в теле каждого цикла. Например:
@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
для предварительного выражения. Используя скобки и точки с запятой, вы можете предоставить многооператорные выражения.
Base.Cartesian.@nref
— Macro@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
— Macro@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
— Macro@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
Base.Cartesian.@ncall
— Macro@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.@ncallkw
— Macro@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
Base.Cartesian.@ntuple
— Macro@ntuple N expr
Генерирует N
-кортеж. @ntuple 2 i
сгенерирует (i_1, i_2)
, а @ntuple 2 k->k+1
сгенерирует (2,3)
.
Base.Cartesian.@nall
— Macro@nall N expr
Проверьте, все ли выражения, сгенерированные выражением анонимной функции expr
, оцениваются как true
.
@nall 3 d->(i_d > 1)
сгенерирует выражение (i_1 > 1 && i_2 > 1 && i_3 > 1)
. Это может быть удобно для проверки границ.
Base.Cartesian.@nany
— Macro@nany N expr
Проверьте, оценивается ли какое-либо из выражений, сгенерированных выражением анонимной функции expr
, как true
.
@nany 3 d->(i_d > 1)
сгенерирует выражение (i_1 > 1 || i_2 > 1 || i_3 > 1)
.
Base.Cartesian.@nif
— Macro@nif N conditionexpr expr
@nif N conditionexpr expr elseexpr
Генерирует последовательность операторов if ... elseif ... else ... end
. Например:
@nif 3 d->(i_d >= size(A,d)) d->(error("Размер ", d, " слишком большой")) d->println("Все в порядке")
сгенерирует:
if i_1 > size(A, 1)
error("Размер ", 1, " слишком большой")
elseif i_2 > size(A, 2)
error("Размер ", 2, " слишком большой")
else
println("Все в порядке")
end