Base.Cartesian
El módulo (no exportado) Cartesian proporciona macros que facilitan la escritura de algoritmos multidimensionales. Más a menudo, puedes escribir tales algoritmos con straightforward techniques; sin embargo, hay algunos casos en los que Base.Cartesian
sigue siendo útil o necesario.
Principles of usage
Un ejemplo simple de uso es:
@nloops 3 i A begin
s += @nref 3 A i
end
que genera el siguiente código:
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
En general, Cartesian te permite escribir código genérico que contiene elementos repetitivos, como los bucles anidados en este ejemplo. Otras aplicaciones incluyen expresiones repetidas (por ejemplo, desenrollado de bucles) o crear llamadas a funciones con un número variable de argumentos sin usar el constructo "splat" (i...
).
Basic syntax
La sintaxis (básica) de @nloops
es la siguiente:
- El primer argumento debe ser un entero (no una variable) que especifique el número de bucles.
- El segundo argumento es el prefijo de símbolo utilizado para la variable del iterador. Aquí usamos
i
, y se generaron las variablesi_1, i_2, i_3
. - El tercer argumento especifica el rango para cada variable de iterador. Si usas una variable (símbolo) aquí, se toma como
axes(A, dim)
. Más flexiblemente, puedes usar la sintaxis de expresión de función anónima descrita a continuación. - El último argumento es el cuerpo del bucle. Aquí, eso es lo que aparece entre
begin...end
.
Hay algunas características adicionales de @nloops
descritas en el reference section.
@nref
sigue un patrón similar, generando A[i_1,i_2,i_3]
a partir de @nref 3 A i
. La práctica general es leer de izquierda a derecha, por lo que @nloops
es @nloops 3 i A expr
(como en for i_2 = axes(A, 2)
, donde i_2
está a la izquierda y el rango está a la derecha) mientras que @nref
es @nref 3 A i
(como en A[i_1,i_2,i_3]
, donde el arreglo viene primero).
Si estás desarrollando código con Cartesian, puede que encuentres que depurar es más fácil cuando examinas el código generado, utilizando @macroexpand
:
julia> @macroexpand @nref 2 A i
:(A[i_1, i_2])
Supplying the number of expressions
El primer argumento de ambos macros es el número de expresiones, que debe ser un entero. Cuando estás escribiendo una función que pretendes que funcione en múltiples dimensiones, esto puede no ser algo que desees codificar de forma rígida. El enfoque recomendado es usar una @generated function
. Aquí hay un ejemplo:
@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
Naturalmente, también puedes preparar expresiones o realizar cálculos antes del bloque quote
.
Anonymous-function expressions as macro arguments
Quizás la característica más poderosa en Cartesian
es la capacidad de proporcionar expresiones de funciones anónimas que se evalúan en el momento del análisis. Consideremos un ejemplo simple:
@nexprs 2 j->(i_j = 1)
@nexprs
genera n
expresiones que siguen un patrón. Este código generaría las siguientes declaraciones:
i_1 = 1
i_2 = 1
En cada declaración generada, un j
"aislado" (la variable de la función anónima) se reemplaza por valores en el rango 1:2
. Hablando en términos generales, Cartesian emplea una sintaxis similar a LaTeX. Esto te permite hacer matemáticas sobre el índice j
. Aquí hay un ejemplo que calcula los pasos de un arreglo:
s_1 = 1
@nexprs 3 j->(s_{j+1} = s_j * size(A, j))
generar expresiones
s_1 = 1
s_2 = s_1 * size(A, 1)
s_3 = s_2 * size(A, 2)
s_4 = s_3 * size(A, 3)
Las expresiones de funciones anónimas tienen muchos usos en la práctica.
Macro reference
Base.Cartesian.@nloops
— Macro@nloops N itersym rangeexpr bodyexpr
@nloops N itersym rangeexpr preexpr bodyexpr
@nloops N itersym rangeexpr preexpr postexpr bodyexpr
Genera N
bucles anidados, utilizando itersym
como el prefijo para las variables de iteración. rangeexpr
puede ser una expresión de función anónima, o un símbolo simple var
, en cuyo caso el rango es axes(var, d)
para la dimensión d
.
Opcionalmente, puedes proporcionar expresiones "pre" y "post". Estas se ejecutan primero y al final, respectivamente, en el cuerpo de cada bucle. Por ejemplo:
@nloops 2 i A d -> j_d = min(i_d, 5) begin
s += @nref 2 A j
end
generaría:
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
Si solo deseas una expresión post, proporciona nothing
para la expresión pre. Usando paréntesis y punto y coma, puedes proporcionar expresiones de múltiples declaraciones.
Base.Cartesian.@nref
— Macro@nref N A indexexpr
Generar expresiones como A[i_1, i_2, ...]
. indexexpr
puede ser un prefijo de símbolo de iteración, o una expresión de función anónima.
Ejemplos
julia> @macroexpand Base.Cartesian.@nref 3 A i
:(A[i_1, i_2, i_3])
Base.Cartesian.@nextract
— Macro@nextract N esym isym
Genera N
variables esym_1
, esym_2
, ..., esym_N
para extraer valores de isym
. isym
puede ser un Symbol
o una expresión de función anónima.
@nextract 2 x y
generaría
x_1 = y[1]
x_2 = y[2]
mientras que @nextract 3 x d->y[2d-1]
produce
x_1 = y[1]
x_2 = y[3]
x_3 = y[5]
Base.Cartesian.@nexprs
— Macro@nexprs N expr
Generar N
expresiones. expr
debe ser una expresión de función anónima.
Ejemplos
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...
Genera una expresión de llamada a función. sym
representa cualquier número de argumentos de función, el último de los cuales puede ser una expresión de función anónima y se expande en N
argumentos.
Por ejemplo, @ncall 3 func a
genera
func(a_1, a_2, a_3)
mientras que @ncall 2 func a b i->c[i]
produce
func(a, b, c[1], c[2])
Base.Cartesian.@ncallkw
— Macro@ncallkw N f kw sym...
Genera una expresión de llamada a función con argumentos de palabra clave kw...
. Como en el caso de @ncall
, sym
representa cualquier número de argumentos de función, el último de los cuales puede ser una expresión de función anónima y se expande en N
argumentos.
Ejemplos
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
Genera un N
-tupla. @ntuple 2 i
generaría (i_1, i_2)
, y @ntuple 2 k->k+1
generaría (2,3)
.
Base.Cartesian.@nall
— Macro@nall N expr
Verifica si todas las expresiones generadas por la expresión de función anónima expr
se evalúan como true
.
@nall 3 d->(i_d > 1)
generaría la expresión (i_1 > 1 && i_2 > 1 && i_3 > 1)
. Esto puede ser conveniente para la verificación de límites.
Base.Cartesian.@nany
— Macro@nany N expr
Verifica si alguna de las expresiones generadas por la expresión de función anónima expr
evalúa a true
.
@nany 3 d->(i_d > 1)
generaría la expresión (i_1 > 1 || i_2 > 1 || i_3 > 1)
.
Base.Cartesian.@nif
— Macro@nif N conditionexpr expr
@nif N conditionexpr expr elseexpr
Genera una secuencia de declaraciones if ... elseif ... else ... end
. Por ejemplo:
@nif 3 d->(i_d >= size(A,d)) d->(error("Dimensión ", d, " demasiado grande")) d->println("Todo OK")
generaría:
if i_1 > size(A, 1)
error("Dimensión ", 1, " demasiado grande")
elseif i_2 > size(A, 2)
error("Dimensión ", 2, " demasiado grande")
else
println("Todo OK")
end