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 позволяет писать универсальный код, содержащий повторяющиеся элементы, например вложенные циклы в этом примере. В число других применений входят повторение выражений (например, разворачивание цикла) или создание вызовов функций с переменным количеством аргументов без использования конструкции «звезда» (i...
).
Основной синтаксис
(Основной) синтаксис макроса @nloops
имеет следующий вид:
-
Первым аргументом должно быть целое число (не переменная), задающее количество циклов.
-
Вторым аргументом является символ-префикс, используемый для переменной-итератора. Здесь использовался
i
, и были сгенерированы переменныеi_1, i_2, i_3
. -
Третий аргумент задает диапазон для каждой переменной итератора. Если здесь использовать переменную (символ) , она будет восприниматься как
axes(A, dim)
. Более гибко можно использовать синтаксис выражения анонимной функции , описанный ниже. -
Последний аргумент является телом цикла. Здесь он появляется между
begin...end
.
Дополнительные возможности макроса @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])
Указание количества выражений
Первым аргументом для обоих макросов является количество выражений, которое должно быть целым числом. При написании функции, которая будет работать в нескольких измерениях, возможно, вы не станете прибегать к жесткому кодированию. Рекомендуется использовать @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
— 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.@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("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