joetjen.net
EN DE
Datenformat

Werte

DXN hat 28 Datentypen in vier Gruppen: Skalare, Sammlungen, Zeitwerte und erweiterte Typen. Jeder hat genau eine Textschreibweise und eine Binärkodierung.

Format 1.0

Auf einen Blick

TypText
nil, booleannil true false
Ganzzahl, Gleitkomma-17 3.14 5e-3 NaN -Infinity
Dezimalzahl, Bruch19.99M 22/7
Zeichenkette, Zeichen"text\n" ?a ?s
Symbol, KeywordPoint ns/name :ok name:
Liste, Tupel, Array[1 2] {:ok 200} @array[1 2]
Map, geordnete Map%{a: 1} %{"k" => 1} @ordered %{…}
Menge, sortierte Menge@{1 2} @sorted-set @{3 1 2}
struct%Point{x: 1 y: 2} %Point[1 2]
Datum, Zeit, Zeitstempel~D[2024-01-01] ~T[12:30:00] ~U[2024-01-01 12:30:00Z]
Datum mit Offset, Dauer@datetime "2024-01-01T12:30:00+02:00" @duration "P1Y2M10D"
UUID, URI, Bytes, Regex@uuid "…" @uri "…" @bytes "SGVsbG8=" ~r/^a+$/i
eigenes Tag@my-app/money 500

Zahlen

Vier Arten, jede genau in dem, was sie ist:

  • Ganzzahl — ein optionales - und Ziffern, mit beliebiger Genauigkeit. Es gibt kein +, kein Hex und keine Zifferntrenner.
  • Gleitkommazahl — ein IEEE-754-Double, mit Nachkommastellen, Exponent oder beidem. NaN, Infinity und -Infinity sind Literale; NaN hat kein Vorzeichen.
  • Dezimalzahl — Ziffern mit nachgestelltem M, exakte Festkommazahl. 19.99M bleibt 19,99, im Text wie binär.
  • Bruch — Zähler/Nenner, gespeichert wie geschrieben: 22/7 und 44/14 sind verschiedene Werte. Ein Nenner von null ist ein Fehler.
[42 -17 12345678901234567890123 3.14 1e3 NaN -Infinity 19.99M -1/3]

Zeichenketten und Zeichen

Zeichenketten sind UTF-8 in doppelten Anführungszeichen und dürfen über Zeilen reichen. Die Escapes sind \", \\, \n, \t, \r, \0, \a, \b, \f, \v und \x{…} mit beliebig vielen Hex-Ziffern für einen Codepoint. Eine Form mit einfachen Anführungszeichen und Interpolation gibt es nicht.

Ein Zeichen ist ? mit einem Codepoint — ein eigener Wert, keine Zeichenkette aus einem Buchstaben. ?s ist das Leerzeichen, und Escapes funktionieren auch nach ?.

["tab\there, smile \x{1F600}" ?a ?s ?\n ?é]

Symbole und Keywords

Ein nackter Bezeichner ist ein Symbol: ein Name, der auf etwas verweist — einen Typ, einen Handler, ein Schema — und nie ausgewertet wird. Ein Keyword ist ein Name als Wert, etwa ein Status oder Modus: :ok, oder :"mit Leerzeichen", wenn es kein einfacher Bezeichner ist. In einer Map ist name: vor einem Wert ein Keyword-Schlüssel.

%{
  handler: billing/invoice-created   # a symbol
  status:  :ok                       # a keyword
  label:   :"needs review"
}

Sammlungen

FormTypBedeutung
[…]Listeeine geordnete Folge, die wachsen darf
{…}Tupeleine feste Gruppe — ein Paar, ein Ergebnis, ein Datensatz
@array[…]arrayeine Folge fester Größe mit Indexzugriff
%{…}mapSchlüssel auf Werte, ohne festgelegte Reihenfolge
@ordered %{…}geordnete Mapeine Map, deren Reihenfolge zu ihrer Identität gehört
@{…}Mengeverschiedene Werte, ungeordnet
@sorted-set @{…}sortierte Mengeeine stets sortierte Menge

Ein Map-Eintrag ist entweder name: wert für Keyword-Schlüssel oder schlüssel => wert für Schlüssel beliebigen Typs. Beide Stile lassen sich mischen. Eine einfache Map verspricht keine Reihenfolge, auch wenn ihre Einträge nacheinander stehen; wo die Reihenfolge zählt, sagt @ordered das ausdrücklich.

%{
  name: "api"
  "Content-Type" => "application/json"
  404 => :not-found
  {52.52 13.40} => "Berlin"
}
@sorted-set @{3 1 2}
@sorted-set @{1 2 3}

Structs

Ein Struct ist ein benannter Datensatz, geschrieben mit Schlüsseln — Feldnamen im Text, in beliebiger Reihenfolge — oder nach Position, mit den Werten in der Reihenfolge, die das Schema festlegt. Beide Formen sind derselbe Wert.

%Point{y: 2, x: 1}
%Point[1, 2]

Ein Struct lässt sich ohne Schema lesen: Ein Leser, der Point nicht kennt, liefert einen undurchsichtigen Wert mit dem Namen und den Feldern, wie sie geschrieben stehen. Mit einem Schema kann er die Felder prüfen, zwischen beiden Formen umwandeln und den Feldern ihre Typen geben.

Datum und Uhrzeit

FormTypBedeutung
~D[2024-01-01]Datumein Kalenderdatum
~T[12:30:00.5]Zeiteine Uhrzeit, bis auf Mikrosekunden
~U[2024-01-01 12:30:00Z]Zeitstempelein Zeitpunkt in UTC — immer mit Z
@datetime "2024-01-01T12:30:00+02:00"datetimeein Zeitpunkt mit einem UTC-Offset ungleich null
@duration "P1Y2M10DT2H30M"Dauereine Dauer nach ISO 8601

UTC-Zeitpunkte und solche mit Offset unterscheiden sich schon in der Syntax: Das Sigil steht für UTC, das Tag für alles andere. Eine Dauer hält Jahre, Monate, Wochen, Tage, Stunden, Minuten und Sekunden als eigene Felder, weil ein Monat oder ein Jahr keine feste Länge hat und sich nicht in eine Sekundenzahl falten lässt.

UUIDs, URIs, Bytes und Regexes

  • @uuid "…" nimmt die 36-stellige Form mit Bindestrichen; alles andere ist ein Fehler.
  • @uri "…" ist eine URI nach RFC 3986, genau so gespeichert, wie sie geschrieben ist.
  • @bytes "…" sind rohe Binärdaten, im Text als Base64, in .dxnb als reine Bytes.
  • ~r/muster/flags ist ein regulärer Ausdruck mit den Flags i m s u x f r — ohne Groß-/Kleinschreibung, mehrzeilig, dotall, Unicode, erweitert, erste Zeile, nicht gierig.
%{
  id:     @uuid "9b74c989-1e2a-4b17-9b1f-2a6a4a2b6f10"
  home:   @uri "https://example.com/path"
  avatar: @bytes "SGVsbG8="
  handle: ~r/^[a-z0-9_]{3,20}$/i
}

Eigene Tags

Jeder Tag-Name, der nicht eingebaut ist, ist ein eigenes Tag: ein Wert, verpackt in einen von der Anwendung festgelegten Namen. Ein Leser mit einem Decoder für den Namen macht daraus seinen eigenen Typ; ein Leser ohne behält ihn als undurchsichtigen Wert mit Tag und gibt ihn weiter. Ein Namensraum im Namen verhindert Kollisionen mit fremden Tags.

@my-app/money {1999 :eur}

Eigene Tags passen zu einem einzelnen verpackten Wert. Alles mit echten Feldern ist als Struct besser aufgehoben, das ein Schema prüfen kann.