Struktur und Typen
Pakete und Built-ins
Dieses Kapitel sammelt die Bibliotheksseite der Sprache: eingebaute Funktionen, Pakete und die wichtigsten Module der Standardbibliothek.
Built-ins#
Built-ins sind ohne Import verfügbar.
Wichtige Gruppen:
- Ausgabe:
print,println,nl(eine Leerzeile, Kurzform fürprintln("")),dd - Listen-Helfer:
head,tail,map,filter,foreach,take,range - String-Helfer:
len,split,chars,upper,lower - Konvertierung:
str,num,int,rat - Keyword-Helfer:
keyword - Introspection:
type - Typ-Praedikate:
int?,rat?,float?,number?,string?,bool?,keyword?,array?,map?,set?,fn?, … (jeweils-> Bool) - Sicherheit/Checks:
assert
Die Undef-Pruefung laeuft ueber den benannten Praefix-Operator defined? expr,
nicht ueber ein Built-in.
Zahlen unterscheiden sich in drei exklusive Kategorien: int? (ganzzahliger
Skalar), rat? (exakter Bruch — auch Dezimalliterale wie 3.14 und Divisionen
wie 1/3) und float? (inexakter Gleitkommawert, z. B. aus Math.sqrt). Jede
Zahl erfuellt genau eines der drei; number? deckt alle ab. type(x) liefert
fuer alle drei einheitlich :number.
Die Kategorie haengt davon ab, wie eine Zahl entsteht, nicht von ihrem Wert:
Math.sqrt(4) ist float? (auch wenn es als 2 angezeigt wird), 4 / 2 ist
rat?. Eine Rechnung mit einem Float ergibt wieder ein Float.
Math.floor, Math.ceil, Math.round und Math.trunc liefern ein int?,
Math.abs, Math.min und Math.max behalten die Kategorie ihres Arguments.
Alle Regeln dazu stehen in 05-zahlen.md.
Regex-nahe Operationen wie match, test und replace sind historisch auch global verfügbar, als saubere API solltest du sie aber eher über Antra::Regex lesen und benutzen. Fuer kompilierte Regex-Werte gibt es ausserdem das Built-in regex?.
Ein typischer Anfängerpfad ist:
println(str(42))
println(len("hello"))
println(map([1, 2, 3], |x| -> x * 2))Fuer dynamisch erzeugte Keyword-Keys gibt es das Built-in keyword(...).
Das ist die explizite Boundary, wenn neuer Code einen String bewusst in einen
dot-kompatiblen Key wie :name umwandeln will.
my key = keyword("verbose")
my opts = map_assoc(%{}, key, true)
println(opts.verbose)
println(opts[key])Fuer grobe Runtime-Introspection gibt es ausserdem type(...). Es liefert ein
Keyword wie :string, :array, :map, :fn oder bei Tagged Values auch
Klassen wie :tagged_scalar, :tagged_string, :tagged_set und
:tagged_map.
import Antra::Value as Value
println(type(42))
println(type(|x| -> x))
println(type(Value.tagged_map("User", %{name: "Ada"})))
println(Value.tag(Value.tagged_map("User", %{name: "Ada"})))print(...) und println(...) akzeptieren jetzt ausser sep: und end:
auch to: fuer einen schreibbaren FileHandle.
import Antra::IO::File as File
import Antra::Result as Result
my fh = File.open("out.txt", [mode: [:write, :create, :truncate]]).!
println("hello", [to: fh])
Result.unwrap!(File.close(fh))Fuer explizite Result-I/O bleibt File.write(...) trotzdem der passendere
Pfad.
dd als Lernwerkzeug#
dd(...) ist besonders hilfreich, wenn du Pattern Matching oder verschachtelte Werte lernst.
my user = %User{name: "Ada", age: 36}
dd(user)Gerade beim Einstieg in Tagged Values spart das viel Rätselraten.
dbg: einen Wert unterwegs ansehen#
dbg(x) schreibt den Wert mit der Zeile auf stderr, einzeilig wie dd ihn
sieht, und liefert ihn zurück — es kann also um jeden Ausdruck stehen
(wie dbg! in Rust oder IO.inspect in Elixir). Ein zweites Argument
benennt die Stelle:
val xs = [1, "a"]
val n = dbg(len(xs), "n") + 1 # stderr: [line 2] n = 2
xs |> dbg() |> len # stderr: [line 3] [1, 'a']println liefert dagegen undef; ein map(xs, |x| -> println(x)) ergibt
also [undef, …] — fuer reine Ausgabe ist foreach/for gedacht.
Pakete definieren#
Pakete schaffen Namespaces:
package Demo {
pub fn greet(name) -> "Hello " <> name
}Nicht erlaubt sind Paketwurzeln, die bereits fuer implizite Sprachtypen
reserviert sind, also z. B. Array, Map, Set, Range, Generator,
String, Number oder Bool. Auch Array::Utils ist deshalb ungueltig.
Importiert wird dann z. B. so:
import Demo
import Demo as DProjektlayout und Suchpfade#
Fuer externe Antra-Projekte ist diese einfache Aufteilung zu empfehlen, aber nicht vorgeschrieben:
src/fuer startbare Skripte wiesrc/main.antlib/fuer importierbare Pakete wielib/Chess/Board.ant
Fuer kleine Skripte oder sehr schlanke Tools darf das Entry-Skript genauso gut
direkt in der Projektwurzel liegen, also z. B. main.ant statt
src/main.ant.
Ein typisches Projekt sieht dann so aus:
chess-tools/
src/
main.ant
lib/
Chess/
Board.ant
PGN.antDie empfohlene Startform ist dann explizit mit -I:
antra -I lib src/main.antBei einem flachen Projekt geht genauso gut:
antra -I lib main.antDanach funktionieren normale Paketimporte wie:
import Chess::Board
import Chess::PGNZur Einordnung der Suchpfade:
- das Verzeichnis des Entry-Skripts wird weiterhin automatisch durchsucht
- zusaetzliche Projektpfade kannst du mit
-I diroder--lib dirvoranstellen - fuer Shells, CI oder Editor-Setups gibt es zusaetzlich
ANTRA_LIB - die eingebaute Antra-Stdlib bleibt immer als Fallback im Suchpfad
Mehrere Projektpfade gehen ebenfalls:
antra -I lib -I vendor/lib src/main.antOder ueber die Umgebung:
ANTRA_LIB=lib:vendor/lib antra src/main.antAuf Nicht-Unix-Systemen verwendet ANTRA_LIB automatisch das dort uebliche
Pfad-Trennzeichen.
Package-Variablen#
Antra kennt innerhalb von Paketen auch packageweite Bindungen wie our oder pub our. Für Einsteiger ist die wichtigste Regel:
- normale lokale Bindungen mit
mysind der Standard - packageweite Bindungen nur dann, wenn der Wert wirklich zum Modulzustand gehört
Praktisch gilt dabei:
our x = ...ist intern im Package sichtbar und rebindbar, also eher wie ein packageweitesmypub our x = ...ist intern und extern lesbar und auch von außen perPkg.x = ...überschreibbarpub const X = ...ist öffentlich lesbar, aber intern wie extern nicht rebindbar- auch bei
ourundpub ourgilt: derselbe Name darf im gleichen Package-Scope nur einmal deklariert werden
Ein Zugriff von außen auf einen privaten our-Member oder auf einen gar nicht
vorhandenen Member ist ein Laufzeitfehler, kein stilles undef:
package Build {
our next_build = 1000 # privat, kein pub
}
Build.next_build # Fehler: 'next_build' is private to package 'Build'
Build.nope # Fehler: package 'Build' has no member 'nope'Das ist Absicht: undef würde „Member ist privat", „Member existiert nicht" und
„Member hat wirklich den Wert undef" ununterscheidbar machen, und ein Tippfehler
wie Build.nextbuild würde unbemerkt weiterlaufen. Der Fehler ist mit try/catch
abfangbar. Wer den Member von außen braucht, deklariert ihn als pub our bzw.
pub const.
Für Pakete, die ihre Member absichtlich erst zur Laufzeit befüllen (z.B. per
Metaprogrammierung), gibt es das Attribut @dynamic — dann fällt ein unbekannter
Zugriff wieder auf undef zurück statt zu werfen. Details dazu und zu den
Warnungen der statischen Prüfung stehen in
17-statische-pruefung-und-warnungen.md.
package Counter {
pub our total = 0
pub fn inc() -> {
total = total + 1
}
}
Counter.inc()
Counter.total = 10
println(Counter.total)Nicht erlaubt ist dagegen:
package Counter {
pub our total = 0
pub our total = 1 # Fehler: gleicher Package-Scope
}Dokumentationskommentare und Metadaten#
Pakete und Funktionen können mit #:-Kommentaren und @attrs versehen werden.
#: Liest eine Datei.
@deprecated("Use read_text")
pub fn read(path) -> "..."Über Antra::Meta lassen sich diese Informationen wieder auslesen.
Eine Doku-Seite erzeugen: antra doc#
Aus denselben Kommentaren baut antra doc eine HTML-Referenz: pro Paket eine
Seite mit dem #:-Text über package, dann jede pub fn, jedes typedef,
jedes protocol und jedes pub field mit Signatur (so, wie sie im Quelltext
steht, ohne Rumpf) und Beschreibung. Der Kommentartext ist Markdown;
`pub` und Namen mit `_` am Anfang bleiben draußen, mehrere Klauseln einer
Funktion stehen untereinander.
```antra
#: Liest die Konfiguration.
#:
#: ```antra
#: Config.load("app.and").! # => %{port: 8080}
#: ```
pub fn load(path :- String) :- Result[Map, String] -> %Ok[%{port: 8080}]
```
```sh
antra doc # docs/ und lib/ -> zig-out/doc/
antra doc --out site # anderer Zielordner
antra doc --docs handbuch --lib src
```
Neben der API nimmt `antra doc` die Markdown-Kapitel unter `docs/` mit: die
Gruppen und die Reihenfolge der Seitenleiste kommen aus `docs/README.md`
(`## Gruppe`, darunter verlinkte Kapitel), die Gruppen der Pakete aus
`docs/stdlib/README.md` (`### Gruppe`, darunter Zeilen mit `Antra::X`). Liegt
zu einem Paket eine Seite `docs/stdlib/X.md` (bei `Antra::Net::HTTP`:
`Net.HTTP.md`), steht ihr Text über der API. Die Seite braucht keinen Server
und lädt nichts nach: `index.html` direkt im Browser öffnen genügt.
## Die Standardbibliothek
Die Pakete unter `Antra::` sind in der [Stdlib-Referenz](stdlib/README.md)
beschrieben: eine Seite pro Paket, mit Erklärungen und der API, die `antra doc`
aus den `#:`-Kommentaren in `lib/Antra/` erzeugt.
## Praktische Lesestrategie für Bibliotheken
Wenn du ein Modul neu ansiehst, starte in dieser Reihenfolge:
1. Welche Tagged Values oder Result-Formen liefert es zurück?
2. Arbeitet es eher funktional oder mutierend?
3. Gibt es ein `!`-Pendant, das bei Fehlern wirft?
4. Ist die API eher Daten-in-Daten-raus oder Callback-orientiert?
## Empfehlung für Einsteiger
Wenn du die Sprache gerade erst lernst, reichen anfangs meist diese Module:
- `Antra::Array`
- `Antra::Result`
- `Antra::String`
- `Antra::Regex`
Die übrigen kannst du später bedarfsgesteuert dazunehmen.
## Referenz: Built-ins im Überblick
Die alte Doku hat diese Built-ins explizit hervorgehoben:
```antra
println(x)
dd(x, [pretty: true, indent: 2, colorize: false])
dbg(x, label)
head(list)
head!(list)
tail(list)
tail!(list)
concat(a, b)
len(str)
str(x)
num(x)
int(x)
rat(x)
assert(cond)
upper(str)
lower(str)
chars(str)
split(str, pattern)
map(list, fn)
filter(list, pred)
foreach(list, fn)
take(n, list)
range(start, end, step)
match(str, pattern)
test(str, pattern)
replace(str, pattern, repl)
keyword(name)
eval(code)
```
`dd(...)` ist fuer Debug-Ausgaben gedacht und gibt den ursprünglichen Wert
zurück. `print(...)`/`println(...)` liefern `undef` — wer einen Wert ausgeben
**und** weiterreichen will, nimmt `dbg`.
Wo ein String **als Literal** dargestellt wird — geschachtelt in Collections
(auch bei `println`), in `dd(...)`, und in Fehlermeldungen, die Werte zeigen —
ist die Anzeige immer round-trip-sicher als Antra-Literal. Wie bei Pythons
`repr` wird die Quote-Art gewählt, die **weniger Escaping** braucht:
```antra
dd("plain") # -> 'plain'
dd("er sagte \"hi\"") # -> 'er sagte "hi"' (hat ", also single)
dd("can't stop") # -> "can't stop" (hat ', also double)
println(%{name: "Ada"}) # -> %{ :name => 'Ada' }
println(%Err["expected 'c'"]) # -> %Err["expected 'c'"] (Apostrophe → double)
```
Ein blanker Top-Level-`println("Klaus")` gibt weiterhin nur den Inhalt aus
(`Klaus`, ohne Quotes) — die Literal-Anzeige greift nur, wenn der String
geschachtelt oder von `dd` gezeigt wird. Andere Serialisierer bleiben bei
ihrer eigenen Konvention: `AND.encode`, `JSON`-Ausgabe und `HTML.render`
verwenden weiter `"..."`.
Aktuell unterstützte `dd(...)`-Optionen:
- `pretty: true|false` schaltet zwischen mehrzeiliger Strukturansicht und kompakter Ein-Zeilen-Ausgabe um
- `indent: n` setzt die Einrückung für Pretty-Ausgabe; `n` muss eine nichtnegative ganze Zahl sein
- `colorize: true|false` aktiviert ANSI-Farben in der Terminal-Ausgabe
- `show_bless: true|false` zeigt bei geblessten Werten zusätzlich das gebundene Package an
- `show_methods: true|false` zeigt bei geblessten Werten zusätzlich öffentliche Methoden an; impliziert intern dieselbe Metadaten-Sicht wie `show_bless`
- `colors: [...]` oder `colors: %{...}` überschreibt einzelne Farben der Palette
Unter `colors:` sind aktuell diese Schlüssel erlaubt:
- `punctuation`
- `tag`
- `keyword`
- `string`
- `number`
- `boolean`
- `undef`
Die Farbwerte müssen Hex-Strings wie `"#rrggbb"` sein.
Beispiel:
```antra
dd(
value,
[
pretty: true,
indent: 4,
colorize: true,
show_bless: true,
show_methods: true,
colors: [
string: "#ffd166",
tag: "#85bf8e"
]
]
)
```
Wichtig fuer den Aufruf:
- bei mehreren Nutzwerten liest `dd(...)` Optionen nur aus dem letzten Argument
- bei genau einem Nutzwert akzeptiert `dd(...)` zusätzlich auch direkt eine Map als Optionsargument
## Referenz: Pakete, `our` und Doku-Metadaten
Package-Variablen aus der alten Referenz:
- `our x = 10` ist intern sichtbar
- `pub our x = 10` ist intern und extern les-/schreibbar
- `pub const PI = 3` ist öffentlich, aber immutable
Dokumentationskommentare verwenden `#:` direkt über `package`, `pub fn` oder `fn`. Attribute wie `@spec`, `@deprecated` oder `@title` werden als Metadaten gespeichert und können über `Antra::Meta` ausgelesen werden.
