Values
DXN has 28 data types in four groups: scalars, collections, temporal values and extended types. Each one has exactly one text spelling and one binary encoding.
At a glance
| Type | Text |
|---|---|
| nil, boolean | nil true false |
| integer, float | -17 3.14 5e-3 NaN -Infinity |
| decimal, rational | 19.99M 22/7 |
| string, char | "text\n" ?a ?s |
| symbol, keyword | Point ns/name :ok name: |
| list, tuple, array | [1 2] {:ok 200} @array[1 2] |
| map, ordered map | %{a: 1} %{"k" => 1} @ordered %{…} |
| set, sorted set | @{1 2} @sorted-set @{3 1 2} |
| struct | %Point{x: 1 y: 2} %Point[1 2] |
| date, time, timestamp | ~D[2024-01-01] ~T[12:30:00] ~U[2024-01-01 12:30:00Z] |
| datetime, duration | @datetime "2024-01-01T12:30:00+02:00" @duration "P1Y2M10D" |
| uuid, uri, bytes, regex | @uuid "…" @uri "…" @bytes "SGVsbG8=" ~r/^a+$/i |
| custom tag | @my-app/money 500 |
Numbers
Four kinds, each exact about what it is:
- Integer — an optional
-and digits, with arbitrary precision. There is no+sign, no hex and no digit separator. - Float — an IEEE 754 double, with a fraction, an exponent or both.
NaN,Infinityand-Infinityare literals;NaNhas no sign. - Decimal — digits with a trailing
M, exact fixed-point.19.99Mstays 19.99, in text and in binary. - Rational —
numerator/denominator, stored as written:22/7and44/14are different values. A zero denominator is an error.
[42 -17 12345678901234567890123 3.14 1e3 NaN -Infinity 19.99M -1/3]
Strings and characters
Strings are UTF-8 in double quotes, and may span lines. The escapes are \", \\, \n, \t, \r, \0, \a, \b, \f, \v, and \x{…} with any number of hex digits for a code point. There is no single-quoted form and no interpolation.
A character is ? and one code point — a value of its own, not a one-letter string. ?s is the space, and escapes work after ? too.
["tab\there, smile \x{1F600}" ?a ?s ?\n ?é]Symbols and keywords
A bare identifier is a symbol: a name that refers to something — a type, a handler, a schema — and is never evaluated. A keyword is a name used as a value, like a status or a mode: :ok, or :"with spaces" when it is not a plain identifier. In a map, name: before a value is a keyword key.
%{
handler: billing/invoice-created # a symbol
status: :ok # a keyword
label: :"needs review"
}Collections
| Form | Type | Meaning |
|---|---|---|
[…] | list | an ordered sequence that may grow |
{…} | tuple | a fixed group — a pair, a result, a record |
@array[…] | array | a fixed-size, indexed sequence |
%{…} | map | keys to values, in no particular order |
@ordered %{…} | ordered map | a map whose order is part of its identity |
@{…} | set | distinct values, unordered |
@sorted-set @{…} | sorted set | a set kept in sorted order |
A map entry is either name: value, for keyword keys, or key => value, for keys of any type. The two styles mix freely. A plain map promises no order even though its entries are written one after another; if order matters, say so with @ordered.
%{
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
A struct is a named record, written keyed — field names inline, in any order — or positional, with the values in the order the schema defines. Both forms are the same value.
%Point{y: 2, x: 1}
%Point[1, 2]A struct parses without a schema: a reader that does not know Point returns an opaque value carrying the name and the fields as written. With a schema, it can check the fields, convert between the two forms and give the fields their types.
Dates and times
| Form | Type | Meaning |
|---|---|---|
~D[2024-01-01] | date | a calendar date |
~T[12:30:00.5] | time | a time of day, up to microseconds |
~U[2024-01-01 12:30:00Z] | timestamp | an instant in UTC — always with Z |
@datetime "2024-01-01T12:30:00+02:00" | datetime | an instant with a non-zero UTC offset |
@duration "P1Y2M10DT2H30M" | duration | an ISO 8601 duration |
UTC and offset instants are told apart by syntax alone: the sigil is for UTC, the tag for everything else. A duration keeps years, months, weeks, days, hours, minutes and seconds as separate fields, because a month or a year has no fixed length and cannot be folded into one number of seconds.
UUIDs, URIs, bytes and regexes
@uuid "…"takes the 36-character hyphenated form; anything else is an error.@uri "…"is an RFC 3986 URI, kept exactly as written.@bytes "…"is raw binary data, base64 in text and plain bytes in.dxnb.~r/pattern/flagsis a regular expression, with the flagsi m s u x f r— caseless, multiline, dotall, unicode, extended, firstline, ungreedy.
%{
id: @uuid "9b74c989-1e2a-4b17-9b1f-2a6a4a2b6f10"
home: @uri "https://example.com/path"
avatar: @bytes "SGVsbG8="
handle: ~r/^[a-z0-9_]{3,20}$/i
}Custom tags
Any tag name that is not built in is a custom tag: one value wrapped in an application-defined name. A reader with a decoder for the name turns it into its own type; a reader without one keeps it as an opaque tagged value and passes it on. Namespace the name to keep it from colliding with someone else’s.
@my-app/money {1999 :eur}Custom tags suit a single wrapped value. Anything with real fields is better written as a struct, which a schema can check.