Escribir \usepackage{expl3} en un LaTeX actual no carga absolutamente nada. expl3 —la capa de programación surgida del proyecto que antes se llamaba «LaTeX3»— ya forma parte del formato. La línea 1125 del fuente del núcleo, latex.ltx, registra justo eso: Skipping: expl3 code already part of the format, y expl3.sty comprueba en la línea 53 si el código ya está presente y, si lo está, descarta la carga entera. Esta página mide qué cambia realmente \ExplSyntaxOn en la lectura de los caracteres y luego recorre cómo leer un nombre como \module_function:nn, qué significan los especificadores de argumentos y los tipos de datos: tl, seq, prop, int, fp y demás.
expl3 ya está en el núcleo: \fpeval es la prueba
TeX nació como procesador de macros, una herramienta para definir comandos con \def y \newcommand. Pero escribir un paquete grande dejó al descubierto lo inconsistentes que son las primitivas en bruto: controlar la expansión y manejar variables se volvió un oficio artesanal. expl3 da nombres nuevos a las primitivas de TeX y e-TeX, nombra funciones y variables de forma sistemática y escribe el tipo de cada argumento en el propio nombre. Levantado durante años por el LaTeX Project, es a la vez biblioteca estándar y lenguaje de programación para LaTeX: xparse, siunitx, fontspec y l3keys2e se apoyan todos en él.
Y esa capa ya no se vende por separado. Abre expl3.sty: las líneas 53 a 59 comprueban si \tex_let:D ya está definido y, si lo está, \@gobble se traga entero el \input expl3-code.tex. Lo que sobrevive como paquete es una cáscara con solo el nombre —se anuncia incluso como L3 programming layer (loader)— mientras la sustancia reside en el formato. En efecto, en un article desnudo, sin nada cargado, \ExplSyntaxOn, \tl_new:N y \ProvidesExplPackage ya están definidos. La prueba más nítida son cuatro líneas a partir de la 1167 de latex.ltx: \fpeval, \inteval, \dimeval y \skipeval se definen con \cs_new_eq:NN como alias de \fp_eval:n, \int_eval:n, \dim_eval:n y \skip_eval:n. El \fpeval{sqrt(2)} del nivel de documento es, pues, la propia función de expl3 llamada con otro nombre (véase la página de contadores y longitudes).
Qué cambia realmente \ExplSyntaxOn: el espacio y ~ intercambian papeles
El código expl3 vive entre \ExplSyntaxOn y \ExplSyntaxOff, y dentro de esa región cambian de código de categoría exactamente cuatro caracteres. Pide al núcleo que imprima \the\catcode antes y después y obtienes esto: el espacio pasa de 10 (espacio) a 9 (carácter ignorado), _ de 8 (subíndice) a 11 (letra), : de 12 (otro) a 11 (letra) y ~ de 13 (activo) a 10 (espacio). \ExplSyntaxOff restaura exactamente 10, 8, 12 y 13. Los códigos de categoría en sí son tema de la página dedicada; lo bonito aquí es que el espacio y ~ han intercambiado asientos. El espacio cede el catcode 10 y se muda al 9, la casilla de «ignorado», y en el 10 vacante se sienta ~. Por eso, dentro de expl3, ~ no es un comando que produzca un espacio: es literalmente un carácter de espacio.
| Carácter | LaTeX normal | Dentro de ExplSyntaxOn |
|---|---|---|
(space) | 10 — espacio | 9 — carácter ignorado. La sangría y los saltos de línea no llegan a la salida |
~ | 13 — activo (espacio duro) | 10 — un espacio en sí. Es lo que se escribe cuando la salida necesita un hueco |
_ | 8 — subíndice | 11 — letra. Puede aparecer dentro de un nombre de comando |
: | 12 — otro carácter | 11 — letra. Puede separar la firma de argumentos |
La consecuencia práctica de ese intercambio es una sola regla: un espacio desnudo dentro de expl3 nunca llega a la salida. Para componer «Fruit: apple» hay que escribir Fruit:~#1; sustituye el ~ por un espacio normal y sale «Fruit:apple» todo pegado. Visto al revés, significa que puedes sangrar el código cuanto quieras y airear los tokens para que se lea mejor sin alterar la salida en absoluto. Dado lo mucho que el LaTeX corriente exige atención con los espacios, es una liberación notable.
% no \usepackage{expl3} is needed: it is in the format
\ExplSyntaxOn
% spaces and newlines here are catcode 9 (ignored); _ and : are letters
\tl_new:N \l_greeting_tl
\tl_set:Nn \l_greeting_tl { Hello,~world! } % ~ is the real space
\tl_use:N \l_greeting_tl
\ExplSyntaxOffCómo leer \seq_put_right:Nn: el nombre declara los tipos
Un nombre de función de expl3 tiene la forma \⟨module⟩_⟨description⟩:⟨arg-signature⟩. Hasta el primer _ está el módulo (un tipo de dato o área de trabajo); hasta el :, el nombre descriptivo; tras el :, la firma de argumentos. En \seq_put_right:Nn el módulo es seq (secuencias), la descripción es put_right (añadir por la derecha) y la firma es Nn. Cada letra de la firma dice cómo se procesa ese argumento antes de pasarlo. Leer el nombre es saber cuántos argumentos toma y qué le ocurre a cada uno: leer los tipos sin consultar el manual es justo el propósito de esta convención.
| Especificador | Significado |
|---|---|
N | Sin manipulación; un token único (por lo general una secuencia de control). |
n | Sin manipulación; un grupo de tokens entre llaves. |
c | Convierte el argumento en una secuencia de control mediante \csname antes de usarlo. |
V / v | Pasa el valor de una variable (V desde un token único, v construye antes el nombre). |
o | Expande el argumento una vez antes de usarlo. |
x / e | Expansión completa (x equivale a \edef y no es expandible; e usa \expanded). |
f | Expande de izquierda a derecha hasta el primer token no expandible. |
p | Un texto de parámetros de TeX (#1#2…); se usa al definir una función. |
T / F | Código a ejecutar cuando la prueba es verdadera / falsa. Suele ir emparejado como TF, como en \tl_if_empty:nTF; también hay variantes solo T y solo F. |
Las variables siguen el mismo estilo pero empiezan por una letra que indica el ámbito. l_ es local (solo cambia dentro del grupo TeX actual), g_ es global, c_ es una constante. Un identificador de tipo cierra el nombre: _tl (lista de tokens), _int (entero), _seq (secuencia), _prop (lista de propiedades), _clist (lista de comas), _fp (coma flotante), _str (cadena), _bool (booleano), etc. Así, \l_my_name_tl se lee de un vistazo como «variable local de lista de tokens» y \g_counter_int como «entero global». Cada módulo ofrece además variables de borrador —temporales desechables— como \l_tmpa_tl y \l_tmpb_int.
Ver la diferencia entre :Nn y :Nx
n guarda lo que escribiste; x graba el valor tal como está ahora mismo. En vez de explicarlo en abstracto, es más rápido volcar el contenido al registro con \tl_show:N. Mete el mismo { [ \l_src_tl ] } en dos variables, una con \tl_set:Nn y otra con \tl_set:Nx, y el registro dice: > \l_a_tl=[\l_src_tl ]. y > \l_b_tl=[FIRST]. La versión n conserva el propio token \l_src_tl; la versión x ya se ha convertido en FIRST, el valor en el momento de la asignación. Cambia ahora \l_src_tl a SECOND y compón ambas: la versión n imprime «[SECOND]» y la versión x, «[FIRST]». Dicho de otro modo, n guarda una referencia y x una instantánea. La mayoría de las situaciones en que se dice que «controlar la expansión es difícil» son en realidad esta elección hecha al revés. \tl_show:N merece recordarse como primer recurso cuando uno se atasca.
\ExplSyntaxOn
\tl_new:N \l_src_tl \tl_set:Nn \l_src_tl { FIRST }
\tl_new:N \l_a_tl \tl_set:Nn \l_a_tl { [ \l_src_tl ] } % reference
\tl_new:N \l_b_tl \tl_set:Nx \l_b_tl { [ \l_src_tl ] } % snapshot
\tl_show:N \l_a_tl % log: > \l_a_tl=[\l_src_tl ].
\tl_show:N \l_b_tl % log: > \l_b_tl=[FIRST].
\tl_set:Nn \l_src_tl { SECOND }
Nn~stored:~\tl_use:N \l_a_tl \par % prints [SECOND]
Nx~stored:~\tl_use:N \l_b_tl \par % prints [FIRST]
\ExplSyntaxOffLos tipos de datos: crear, asignar, usar
expl3 se divide en módulos por tipo de dato, y todos comparten el mismo ritmo: crear, asignar, usar. Se declara con \⟨type⟩_new:N, se mete un valor con \⟨type⟩_set:Nn y se saca con \⟨type⟩_use:N o alguno de sus hermanos. Los gestos no cambian al cambiar el tipo, así que aprender uno permite adivinar los demás. Solo la definición de funciones tiene otra forma: \cs_new:Npn define una función nueva y da error si ya existe una con ese nombre (cs es control sequence). \cs_set:Npn también define, pero solo dentro del grupo TeX actual, y no protesta ante una redefinición. Ambas usan :Npn, donde N es la función a definir, p su texto de parámetros (#1#2…) y n el cuerpo, el texto de reemplazo.
| Módulo | Qué contiene | Funciones habituales y resultados medidos |
|---|---|---|
tl | Lista de tokens: la variable más básica, usable casi como una cadena | \tl_new:N / \tl_set:Nn (el contenido anterior se pierde) / \tl_use:N / \tl_show:N |
str | Cadenas: para comparar como caracteres y no como tokens | \str_if_eq:nnTF { a~b } { a~b } devuelve same |
int | Enteros: aritmética entera con los operadores habituales | \int_eval:n { 2 + 3 * 4 } da 14 (\inteval es su alias) |
fp | Coma flotante: con funciones científicas como sin, sqrt, pi | \fp_eval:n { sqrt(2) } da 1.414213562373095; \fp_eval:n { 2 * pi } da 6.283185307179586 |
seq | Secuencias: listas con acceso por ambos extremos (también como pilas) | \seq_put_right:Nn añade, \seq_map_inline:Nn recorre cada elemento como #1, \seq_use:Nn une con un separador |
prop | Listas de propiedades: diccionarios que asocian claves a valores | \prop_put:Nnn guarda variable, clave y valor; \prop_item:Nn recupera |
clist | Una lista de valores separados por comas | Tras \clist_set:Nn \l_c_clist { 1,2,3 }, \clist_use:Nn \l_c_clist { ~+~ } da «1 + 2 + 3» |
bool | Booleanos: lo que se usa en lugar de \newif | \bool_new:N / \bool_set_true:N / \bool_if:NTF para bifurcar |
\documentclass{article}
\begin{document}
\ExplSyntaxOn
\seq_new:N \l_example_fruits_seq
% define a function that appends one item
\cs_new:Npn \example_add:n #1
{ \seq_put_right:Nn \l_example_fruits_seq {#1} }
\example_add:n { apple }
\example_add:n { banana }
\example_add:n { cherry }
% each item arrives as #1; ~ is a real space
\seq_map_inline:Nn \l_example_fruits_seq
{ Fruit:~#1 \par }
\ExplSyntaxOff
\end{document}Compila eso y «Fruit: apple», «Fruit: banana» y «Fruit: cherry» salen en tres líneas separadas. El #1 de \cs_new:Npn es el argumento de la función definida, mientras que el de \seq_map_inline:Nn es cada elemento recorrido; ambos llegan como tipo n, el contenido entre llaves. Que los _ y : de los nombres de comando no den problema se debe precisamente a que estamos en una región \ExplSyntaxOn.
l3keys y cómo se usa expl3 al escribir un paquete
Cuando quieras opciones de la forma key = value en tus propios comandos o paquetes, la herramienta estándar es l3keys. Declara claves con \keys_define:nn { module } { ... } y fíjalas con \keys_set:nn { module } { key = value }. Una declaración de clave lleva una propiedad pospuesta como .tl_set:N (guardar en una variable de lista de tokens), .bool_set:N (convertirla en booleano), .code:n (ejecutar código arbitrario) o .initial:n (valor por defecto). Esta maquinaria se conectaba originalmente a las opciones de paquete mediante un paquete llamado l3keys2e, pero su núcleo se ha absorbido desde entonces en el núcleo de LaTeX2ε y está disponible sin ningún paquete como \DeclareKeys y \ProcessKeyOptions (la página de autoría de paquetes escribe uno).
Para la escritura corriente de documentos casi nunca hará falta escribir expl3 directamente. Pero en cuanto te propongas escribir un paquete o una clase, expl3 es hoy el estándar de facto. Un emparejamiento habitual es recibir el comando de cara al usuario con \NewDocumentCommand (xparse) e implementar su cuerpo en expl3. Conviene separar dos cosas: la especificación de argumentos de xparse (nivel de documento, como m, O{...}, s) y la firma de argumentos de expl3 (nivel de programación, como N, n) no son lo mismo. La primera se trata en detalle en la página de xparse. Y si la primera línea de tu .sty es \ProvidesExplPackage, la sintaxis expl3 se activa automáticamente desde ese punto: no hace falta escribir \ExplSyntaxOn.
- Encierra el código entre
\ExplSyntaxOn…\ExplSyntaxOff. Dentro, un espacio desnudo pasa a código de categoría 9 (ignorado); usa~cuando la salida necesite un hueco. - No hace falta
\usepackage{expl3}: está en el núcleo, yexpl3.stydescarta la carga por sí mismo. En un.sty,\ProvidesExplPackageevita también\ExplSyntaxOn. - Las funciones son
\⟨module⟩_⟨description⟩:⟨signature⟩; las variables,\⟨scope⟩_⟨name⟩_⟨type⟩, con ámbitol_/g_/c_. - No confundas
nconx. Elige según quieras conservar una referencia o grabar el valor actual. Ante la duda, imprímelo con\tl_show:Ny míralo. - No inventes nombres de comando. La nomenclatura es estricta; el manual oficial interface3 (
texdoc interface3) es la fuente primaria.