Synsemadocsv0.6.xENES

Lenguaje

Tipos y datos

Valores base: text, number, bool, list, map y nothing. Además: decimal (base 10 exacto, para plata), bytes, complex, array numérico, y los tipos de tiempo date, datetime y duration — ver Stdlib. Una lambda o una task también es un valor.

types.syn
-- Doc example: types, property access, custom types, value semantics, map/list helpers,
-- conversions and the intentional operations.
intent: "doc example: types and data"

type Point
    x: number
    y: number

let demo_p be Point(3, 4)
print("Point(3,4) → x=" + text(x of demo_p) + " y=" + text(y of demo_p))

-- The message of the error `f` raises, or nothing when it does not raise.
task error_of(f)
    try
        f()
    recover err
        give err
    give nothing

task add_tag(item)
    set item["tag"] to "new"
    give item

test "type_of names every kind of value"
    assert_eq(type_of("a"), "text")
    assert_eq(type_of(1), "number")
    assert_eq(type_of(1.5), "number")
    assert_eq(type_of(true), "bool")
    assert_eq(type_of([1]), "list")
    assert_eq(type_of({"a": 1}), "map")
    assert_eq(type_of(nothing), "nothing")
    assert_eq(type_of(1.5d), "decimal")
    assert_eq(type_of(bytes([1])), "bytes")
    assert_eq(type_of(array([1, 2])), "array")
    assert_eq(type_of(complex(1, 2)), "complex")
    assert_eq(type_of(date(2026, 1, 3)), "date")
    assert_eq(type_of(datetime("2026-01-03T10:00:00Z")), "datetime")
    assert_eq(type_of(duration(hours = 1)), "duration")
    assert_eq(type_of((x) => x), "task")
    assert(is_integer(1) and is_text("a") and is_list([]) and is_map({}))
    assert_eq(is_integer(1.0), false)

test "integers are exact at any size; decimals are exact; floats are floats"
    assert_eq(2 ** 64, 18446744073709551616)
    assert_eq(1.1d + 2.2d, 3.3d)
    assert_eq(0.1 + 0.2 == 0.3, false)
    assert_error(() => 1d == 1.0)

test "conversions: text, int, number, hex"
    assert_eq(text(42), "42")
    assert_eq(int("42"), 42)
    assert_eq(int("0x1f"), 31)
    assert_eq(int("ff", base = 16), 255)
    assert_eq(int("abc", fallback = 0), 0)
    assert_eq(number("42"), 42.0)
    assert_eq(hex(255), "0xff")
    assert_error(() => int(1.5))

test "values + property access (of / dot / index)"
    let cfg be {"host": "localhost", "port": 8080}
    assert_eq(port of cfg, 8080)
    assert_eq(cfg.host, "localhost")
    assert_eq(cfg["port"], 8080)
    assert_eq(length([1, 2, 3]), 3)
    assert_eq([1, 2, 3][-1], 3)
    assert(contains(error_of(() => cfg["user"]), "Map has no key 'user'"))
    assert_eq(get(cfg, "user", "anon"), "anon")

test "custom type + positional constructor"
    let p be Point(3, 4)
    assert_eq(x of p, 3)
    assert_eq(y of p, 4)
    assert_eq(p, {"x": 3, "y": 4})
    assert(contains(error_of(() => Point(3)), "expects 2 fields, got 1"))

test "lists and maps are values: copies are independent"
    let a be {"n": 1}
    let b be a
    set b["n"] to 2
    assert_eq(a["n"], 1)
    let item be {"id": 7}
    let tagged be add_tag(item)
    assert_eq(item, {"id": 7})
    assert_eq(tagged, {"id": 7, "tag": "new"})
    let p be Point(3, 4)
    let q be p
    set q.y to 99
    assert_eq(p.y, 4)

test "map helpers: get, items, keys, values, merge, remove, in"
    let m be {"a": 1, "b": 2}
    assert_eq(get(m, "a"), 1)
    assert_eq(get(m, "z"), nothing)
    assert_eq(get(m, "z", 0), 0)
    assert_eq(items(m), [{"key": "a", "value": 1}, {"key": "b", "value": 2}])
    assert_eq(keys(m), ["a", "b"])
    assert_eq(values(m), [1, 2])
    assert_eq(merge(m, {"b": 3}, {"c": 4}), {"a": 1, "b": 3, "c": 4})
    assert_eq(remove(m, "a"), {"b": 2})
    assert_eq(m, {"a": 1, "b": 2})
    assert("a" in m)
    set m["c"] to 3
    assert_eq(length(m), 3)

test "list helpers: append, insert, get, slice, index_of, reverse"
    let xs be [1, 2, 3]
    assert_eq(append(xs, 4), [1, 2, 3, 4])
    assert_eq(insert(xs, 0, 0), [0, 1, 2, 3])
    assert_eq(insert(xs, -1, 9), [1, 2, 9, 3])
    assert_eq(get(xs, 5, "none"), "none")
    assert_eq(slice(xs, 1), [2, 3])
    assert_eq(index_of(xs, 3), 2)
    assert_eq(index_of(xs, 7), nothing)
    assert_eq(reverse(xs), [3, 2, 1])
    assert_eq(xs + [4], [1, 2, 3, 4])
    assert_eq(xs, [1, 2, 3])

test "intentional ops replace loops"
    let nums be [1, 2, 3, 4]
    assert_eq(apply((n) => n * 2, nums), [2, 4, 6, 8])
    assert_eq(where(nums, (n) => n > 2), [3, 4])
    assert_eq(reduce(nums, (a, b) => a + b, 0), 10)
    assert_eq(sort([3, 1, 2]), [1, 2, 3])
    assert_eq(sort([3, 1, 2], desc = true), [3, 2, 1])
    assert_eq(sort([2, nothing, 1], desc = true), [2, 1, nothing])
    assert_eq(sort_by([3, 1, 2], (v) => v), [1, 2, 3])
    assert_eq(sort_by([3, 1, 2], (v) => v, desc = true), [3, 2, 1])
    assert_eq(collect([{"a": 1}, {"a": 2}], "a"), [1, 2])
    assert_eq(count_where(nums, (n) => n % 2 == 0), 2)
    assert_eq(apply(enumerate(["a", "b"]), (e) => e.index), [0, 1])

Qué es un valor§

type_of(x) lo nombra: "text", "number", "bool", "list", "map", "nothing", "decimal", "bytes", "complex", "array", "date", "datetime", "duration", "task". Los chequeos is_integer(x), is_text(x), is_list(x) e is_map(x) responden una pregunta cada uno (is_integer(1.0) es false: es un float con valor entero).

18446744073709551616); / siempre da un float, // mantiene exactos los enteros. 0.1 + 0.2 es un float y no es 0.3; para plata usá decimales (1.1d + 2.2d == 3.3d). Comparar un decimal con un float es error — convertí uno a propósito.

entero, con la base y el fallback por nombre — int("ff", base = 16), int("abc", fallback = 0); int(1.5) es error (usá floor, round o trunc); number(x) → siempre un float; hex(n) → "0xff".

Acceso a propiedades — tres formas§

host of config       -- natural
config.host          -- dot
config["host"]       -- index

Una clave que falta es error (Map has no key 'user'); get(config, "user", "anon") devuelve un default en su lugar. Las listas, el texto y los bytes aceptan índices negativos (xs[-1] es el último item).

Tipos personalizados§

Un type declara campos con nombre; el constructor es posicional en el orden de los campos y toma exactamente un argumento por campo (Type Point expects 2 fields, got 1). El resultado es un map con esos campos:

type Customer
    name: text
    email: text
    balance: number

let c be Customer("Ada", "ada@x.com", 500)
let who be name of c

Las listas y los maps son valores§

Bindear una lista o un map, o pasárselo a una task, da una copia lógica (copy-on-write: no se copia nada hasta que alguien escribe, así que pasarlos no cuesta nada):

let a be {"n": 1}
let b be a
set b["n"] to 2          -- a["n"] is still 1

Una task que hace set adentro de un map que recibió cambia su propia copia: para devolver el cambio, hacé give y set en el llamador (set item to add_tag(item)). Lo mismo con each r in rows — set r["x"] to 1 cambia la copia del loop; escribí por índice (set rows[i]["x"] to 1) o reconstruí con apply + merge. El estado compartido es siempre explícito: el blackboard, memory, el bus, state_*, o una variable exportada de un módulo (Módulos). set xs to append(xs, x), set m[k] to v y set xs to insert(xs, i, v) actualizan en el lugar cuando nadie más tiene el valor, así que armar una lista en un loop sigue siendo rápido.

Helpers de maps y listas§

Cada helper devuelve un valor NUEVO y deja su argumento intacto:

(un i negativo cuenta desde el final).

fuera de rango es error), slice(xs, start, end?), index_of(xs, v) (nothing si no está), reverse(xs), enumerate(xs) → [{index, item}, …] (motor v0.6.46+: también un array, por su primer eje).

Operaciones intencionales (en vez de loops)§

Expresá la intención y dejá que el runtime itere:

Se lee mejor que un loop, y es lo primero a lo que un LLM debería recurrir. Las tablas (listas de maps) tienen sus propias operaciones — summarize, group_by, join, pivot — en CSV, estadística y gráficos.