expl3 / LaTeX3-Schicht

\usepackage{expl3} lädt in aktuellem LaTeX überhaupt nichts. expl3 – die Programmierschicht, die aus dem einst „LaTeX3“ genannten Vorhaben hervorging – ist längst Teil des Formats. Zeile 1125 der Kernelquelle latex.ltx protokolliert genau das: Skipping: expl3 code already part of the format, und expl3.sty prüft in Zeile 53, ob der Code schon vorhanden ist, und verwirft dann das gesamte Laden. Diese Seite misst nach, was \ExplSyntaxOn wirklich am Einlesen von Zeichen ändert, und geht dann durch, wie ein Name wie \module_function:nn zu lesen ist, was die Argumentspezifizierer bedeuten und welche Datentypen es gibt – tl, seq, prop, int, fp und weitere.

expl3 steckt bereits im Kernel – \fpeval ist der Beweis

TeX begann als Makroprozessor, als Werkzeug, um mit \def und \newcommand Befehle zu definieren. Beim Schreiben großer Pakete zeigte sich jedoch, wie uneinheitlich die rohen Primitive sind: Expansionskontrolle und Variablenbehandlung wurden zum Handwerk. expl3 gibt den TeX- und e-TeX-Primitiven neue Namen, benennt Funktionen und Variablen systematisch und schreibt den Typ jedes Arguments in den Namen selbst hinein. Über viele Jahre vom LaTeX Project aufgebaut, ist es Standardbibliothek und Programmiersprache für LaTeX zugleich – xparse, siunitx, fontspec und l3keys2e stehen sämtlich darauf.

Und diese Schicht wird nicht mehr getrennt verkauft. In expl3.sty prüfen die Zeilen 53 bis 59, ob \tex_let:D schon definiert ist; falls ja, verschluckt \@gobble das \input expl3-code.tex vollständig. Als Paket bleibt eine Hülle mit bloßem Namen – sie meldet sich sogar als L3 programming layer (loader) –, während die Substanz im Format liegt. Tatsächlich sind in einem nackten article ohne jedes Paket \ExplSyntaxOn, \tl_new:N und \ProvidesExplPackage bereits definiert. Der klarste Beleg sind vier Zeilen ab latex.ltx Zeile 1167: \fpeval, \inteval, \dimeval und \skipeval werden mit \cs_new_eq:NN als Aliase von \fp_eval:n, \int_eval:n, \dim_eval:n und \skip_eval:n definiert. Das dokumentnahe \fpeval{sqrt(2)} ist also die expl3-Funktion selbst, nur unter anderem Namen (siehe die Seite zu Zählern und Längen).

Was \ExplSyntaxOn wirklich ändert: Leerzeichen und ~ tauschen die Rollen

expl3-Code steht zwischen \ExplSyntaxOn und \ExplSyntaxOff, und in diesem Bereich ändern genau vier Zeichen ihren Kategoriecode. Lässt man den Kernel \the\catcode vorher und nachher ausgeben, ergibt sich: Das Leerzeichen wechselt von 10 (Leerzeichen) auf 9 (ignoriertes Zeichen), _ von 8 (Index) auf 11 (Buchstabe), : von 12 (sonstiges) auf 11 (Buchstabe) und ~ von 13 (aktiv) auf 10 (Leerzeichen). \ExplSyntaxOff stellt exakt 10, 8, 12 und 13 wieder her. Die Kategoriecodes selbst behandelt die Catcode-Seite; reizvoll ist hier, dass Leerzeichen und ~ die Plätze getauscht haben. Das Leerzeichen räumt Catcode 10 und zieht auf die 9, den Platz für „ignoriert“, und auf die frei gewordene 10 setzt sich ~. Deshalb ist ~ innerhalb von expl3 kein Befehl, der ein Leerzeichen erzeugt – es ist buchstäblich ein Leerzeichen.

ZeichenNormales LaTeXInnerhalb von ExplSyntaxOn
(space)10 – Leerzeichen9 – ignoriertes Zeichen. Einrückung und Zeilenumbrüche wirken sich nicht auf die Ausgabe aus
~13 – aktiv (geschütztes Leerzeichen)10 – selbst ein Leerzeichen. Das schreibt man, wenn die Ausgabe eine Lücke braucht
_8 – Index11 – Buchstabe. Darf in einem Befehlsnamen stehen
:12 – sonstiges Zeichen11 – Buchstabe. Kann die Argumentsignatur abtrennen

Die praktische Folge dieses Tauschs ist eine einzige Regel: Ein rohes Leerzeichen innerhalb von expl3 gelangt nie in die Ausgabe. Um „Fruit: apple“ zu setzen, muss Fruit:~#1 geschrieben werden; ersetzt man ~ durch ein gewöhnliches Leerzeichen, klebt daraus „Fruit:apple“. Umgekehrt heißt das: Code darf beliebig eingerückt und mit Abständen zwischen Token lesbar gemacht werden, ohne die Ausgabe im Geringsten zu verändern. Bedenkt man, wie viel Aufmerksamkeit normales LaTeX für Leerzeichen verlangt, ist das eine beachtliche Befreiung.

document.tex
% 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
\ExplSyntaxOff

\seq_put_right:Nn lesen – der Name nennt die Typen

Ein expl3-Funktionsname hat die Form \⟨module⟩_⟨description⟩:⟨arg-signature⟩. Bis zum ersten _ steht das Modul (ein Datentyp oder Arbeitsbereich), bis zum : folgt der beschreibende Name, danach die Argumentsignatur. In \seq_put_right:Nn ist seq das Modul (Sequenzen), put_right die Beschreibung (rechts anhängen) und Nn die Signatur. Jeder Buchstabe der Signatur sagt, wie das jeweilige Argument vor der Übergabe behandelt wird. Wer den Namen liest, weiß, wie viele Argumente er nimmt und was mit jedem geschieht – die Typen ohne Handbuch ablesen zu können, ist genau der Zweck dieses Schemas.

SpezifiziererBedeutung
NKeine Veränderung; ein einzelnes Token (meist eine Kontrollsequenz).
nKeine Veränderung; eine geklammerte Tokenliste.
cWandelt das Argument über \csname in eine Kontrollsequenz um.
V / vÜbergibt den Wert einer Variablen (V von einem Einzeltoken, v baut den Namen zuerst).
oExpandiert das Argument einmal vor der Verwendung.
x / eVollständige Expansion (x wie \edef, nicht expandierbar; e nutzt \expanded).
fExpandiert von links nach rechts bis zum ersten nicht expandierbaren Token.
pEin TeX-Parametertext (#1#2…), beim Definieren einer Funktion.
T / FCode für den Fall, dass die Prüfung wahr / falsch ist. Meist als TF gepaart, etwa \tl_if_empty:nTF; es gibt auch reine T- und F-Varianten.

Variablen folgen demselben Stil, beginnen aber mit einem Buchstaben für den Gültigkeitsbereich. l_ ist lokal (nur innerhalb der aktuellen TeX-Gruppe veränderlich), g_ ist global, c_ eine Konstante. Am Ende steht der Typ: _tl (Tokenliste), _int (Ganzzahl), _seq (Sequenz), _prop (Property List), _clist (Kommaliste), _fp (Gleitkomma), _str (Zeichenkette), _bool (Wahrheitswert) und so fort. \l_my_name_tl liest sich damit sofort als „lokale Tokenlistenvariable“, \g_counter_int als „globale Ganzzahl“. Jedes Modul stellt zudem Scratch-Variablen bereit – Wegwerf-Zwischenspeicher wie \l_tmpa_tl und \l_tmpb_int.

Den Unterschied zwischen :Nn und :Nx sichtbar machen

n speichert das Geschriebene, x brennt den Wert von genau jetzt ein. Statt das abstrakt zu erklären, ist es schneller, den Inhalt mit \tl_show:N ins Log zu schreiben. Legt man dasselbe { [ \l_src_tl ] } einmal mit \tl_set:Nn und einmal mit \tl_set:Nx in zwei Variablen, steht im Log: > \l_a_tl=[\l_src_tl ]. und > \l_b_tl=[FIRST]. Die n-Fassung hält das Token \l_src_tl selbst, die x-Fassung ist bereits zu FIRST geworden, dem Wert zum Zeitpunkt der Zuweisung. Ändert man nun \l_src_tl zu SECOND und setzt beide, druckt die n-Fassung „[SECOND]“, die x-Fassung „[FIRST]“. Mit anderen Worten: n bewahrt eine Referenz, x eine Momentaufnahme. Die meisten Lagen, in denen „Expansionskontrolle schwierig“ heißt, sind in Wahrheit diese Wahl in der falschen Richtung. \tl_show:N lohnt sich als erster Griff, wenn es klemmt.

document.tex
\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]
\ExplSyntaxOff

Die Datentypen: erzeugen, setzen, verwenden

expl3 ist nach Datentypen in Module aufgeteilt, und alle folgen demselben Rhythmus: erzeugen, setzen, verwenden. Deklariert wird mit \⟨type⟩_new:N, ein Wert kommt mit \⟨type⟩_set:Nn hinein, heraus geht es mit \⟨type⟩_use:N oder Verwandten. Die Handgriffe ändern sich mit dem Typ nicht, wer einen lernt, errät die übrigen. Nur die Funktionsdefinition sieht etwas anders aus: \cs_new:Npn definiert eine neue Funktion und meldet einen Fehler, falls der Name schon existiert (cs steht für control sequence). \cs_set:Npn definiert ebenfalls, aber nur innerhalb der aktuellen TeX-Gruppe und ohne Beschwerde bei Neudefinition. Beide verwenden :Npn: N ist die zu definierende Funktion, p ihr Parametertext (#1#2…), n der Körper, also der Ersetzungstext.

ModulWas es enthältHäufige Funktionen mit gemessenen Werten
tlTokenliste – die einfachste Variable, ähnlich einer Zeichenkette nutzbar\tl_new:N / \tl_set:Nn (der alte Inhalt entfällt) / \tl_use:N / \tl_show:N
strZeichenketten – zum Vergleich als Zeichen statt als Token\str_if_eq:nnTF { a~b } { a~b } liefert same
intGanzzahlen – Ganzzahlarithmetik mit den üblichen Operatoren\int_eval:n { 2 + 3 * 4 } ergibt 14 (\inteval ist der Alias)
fpGleitkomma – mit wissenschaftlichen Funktionen wie sin, sqrt, pi\fp_eval:n { sqrt(2) } ergibt 1.414213562373095, \fp_eval:n { 2 * pi } ergibt 6.283185307179586
seqSequenzen – Listen mit Zugriff an beiden Enden (auch als Stapel nutzbar)\seq_put_right:Nn hängt an, \seq_map_inline:Nn durchläuft jedes Element als #1, \seq_use:Nn verbindet mit einem Trenner
propProperty Lists – Wörterbücher, Schlüssel auf Werte abbildend\prop_put:Nnn speichert Variable, Schlüssel, Wert; \prop_item:Nn liest aus
clistEine kommagetrennte WertelisteNach \clist_set:Nn \l_c_clist { 1,2,3 } ergibt \clist_use:Nn \l_c_clist { ~+~ } „1 + 2 + 3“
boolWahrheitswerte – der Ersatz für \newif\bool_new:N / \bool_set_true:N / \bool_if:NTF zum Verzweigen
document.tex
\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}

Übersetzt man das, erscheinen „Fruit: apple“, „Fruit: banana“ und „Fruit: cherry“ auf drei getrennten Zeilen. Das #1 in \cs_new:Npn ist das Argument der definierten Funktion, das #1 in \seq_map_inline:Nn das jeweils durchlaufene Element – beide kommen als n-Typ an, also als geklammerter Inhalt. Dass _ und : in den Befehlsnamen keinerlei Ärger machen, liegt genau daran, dass hier ein \ExplSyntaxOn-Bereich vorliegt.

l3keys und der Einsatz beim Schreiben eines Pakets

Sollen eigene Befehle oder Pakete Optionen der Form key = value erhalten, ist l3keys das Standardwerkzeug. Schlüssel werden mit \keys_define:nn { module } { ... } deklariert und mit \keys_set:nn { module } { key = value } gesetzt. Eine Schlüsseldeklaration trägt eine nachgestellte Eigenschaft wie .tl_set:N (in eine Tokenlistenvariable ablegen), .bool_set:N (als Wahrheitswert), .code:n (beliebigen Code ausführen) oder .initial:n (Vorgabewert). Ursprünglich war diese Maschinerie über ein Paket namens l3keys2e an Paketoptionen angebunden; ihr Kern wurde inzwischen in den LaTeX2ε-Kernel übernommen und steht ganz ohne Paket als \DeclareKeys und \ProcessKeyOptions bereit (die Seite zur Paketentwicklung schreibt so etwas aus).

Beim gewöhnlichen Schreiben von Dokumenten wird man expl3 kaum je direkt brauchen. Sobald aber ein Paket oder eine Klasse entsteht, ist expl3 heute praktisch der Standard. Häufig nimmt man die nutzerseitige Schnittstelle mit \NewDocumentCommand (xparse) entgegen und implementiert den Körper in expl3. Zweierlei ist auseinanderzuhalten: Die Argumentspezifikation von xparse (Dokumentebene, etwa m, O{...}, s) und die Argumentsignatur von expl3 (Programmierebene, etwa N, n) sind nicht dasselbe. Erstere behandelt die xparse-Seite ausführlich. Steht in der ersten Zeile einer .sty \ProvidesExplPackage, schaltet die expl3-Syntax von da an automatisch ein – \ExplSyntaxOn entfällt vollständig.

  • Code in \ExplSyntaxOn\ExplSyntaxOff einschließen. Darin wird ein rohes Leerzeichen zu Kategoriecode 9 (ignoriert); braucht die Ausgabe eine Lücke, ~ verwenden.
  • Kein \usepackage{expl3} nötig – es steckt im Kernel, und expl3.sty verwirft das Laden selbst. In einer .sty erübrigt \ProvidesExplPackage auch \ExplSyntaxOn.
  • Funktionen heißen \⟨module⟩_⟨description⟩:⟨signature⟩, Variablen \⟨scope⟩_⟨name⟩_⟨type⟩ mit Scope l_ / g_ / c_.
  • n und x nicht verwechseln. Die Wahl richtet sich danach, ob eine Referenz erhalten bleiben oder der aktuelle Wert eingebrannt werden soll. Im Zweifel mit \tl_show:N ausgeben und nachsehen.
  • Keine Befehlsnamen erfinden. Die Benennung ist strikt; das offizielle interface3-Handbuch (texdoc interface3) ist die Primärquelle.