Antra

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ür println("")), 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 D

Projektlayout und Suchpfade#

Fuer externe Antra-Projekte ist diese einfache Aufteilung zu empfehlen, aber nicht vorgeschrieben:

  • src/ fuer startbare Skripte wie src/main.ant
  • lib/ fuer importierbare Pakete wie lib/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.ant

Die empfohlene Startform ist dann explizit mit -I:

antra -I lib src/main.ant

Bei einem flachen Projekt geht genauso gut:

antra -I lib main.ant

Danach funktionieren normale Paketimporte wie:

import Chess::Board
import Chess::PGN

Zur Einordnung der Suchpfade:

  • das Verzeichnis des Entry-Skripts wird weiterhin automatisch durchsucht
  • zusaetzliche Projektpfade kannst du mit -I dir oder --lib dir voranstellen
  • 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.ant

Oder ueber die Umgebung:

ANTRA_LIB=lib:vendor/lib antra src/main.ant

Auf 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 my sind 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 packageweites my
  • pub our x = ... ist intern und extern lesbar und auch von außen per Pkg.x = ... überschreibbar
  • pub const X = ... ist öffentlich lesbar, aber intern wie extern nicht rebindbar
  • auch bei our und pub our gilt: 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.

Quelle dieser Seite