Synsemadocsv0.6.xENES

Lenguaje

Control de flujo y errores

control.syn
-- Doc example: control flow (when / match / each / while) and errors (try / recover / raise).
intent: "doc example: control flow and errors"

enum Status
    paid(amount)
    shipped(days, carrier)
    open

task grade(score)
    when score >= 90
        give "A"
    otherwise when score >= 70
        give "B"
    otherwise
        give "C"

task status_label(status)
    match status
        is "open"
            give "active"
        is "done"
            give "archived"
        otherwise
            give "unknown"

task describe(v)
    match v
        is 0
            give "zero"
        is Status.paid(amount)
            give "paid " + text(amount)
        is Status.shipped(d, c) when c == "DHL"
            give "express"
        is [first, ...rest]
            give "list of " + text(1 + length(rest)) + " starting at " + text(first)
        is {name, age} when age >= 18
            give "adult " + name
        is {status: 200, body}
            give "ok: " + body
        is _
            give "something else"

task shape(xs)
    match xs
        is []
            give "empty"
        is [a, b]
            give "pair " + text(a + b)
        is [...init, last]
            give "last " + text(last) + " after " + text(length(init))

task is_target(v, target)
    match v
        is target
            give true
        otherwise
            give false

task sum_list(xs)
    let total be 0
    each n in xs
        set total to total + n
    give total

task always_fails()
    raise("boom")

task fails_as_statement()
    raise "bad input"

task give_inside_try()
    try
        give "given"
    recover err
        give "recovered"

print("grade(85) = " + grade(85) + ",  sum_list([1,2,3]) = " + text(sum_list([1, 2, 3])))

test "when / otherwise when / otherwise"
    assert_eq(grade(95), "A")
    assert_eq(grade(75), "B")
    assert_eq(grade(50), "C")

test "match dispatches on value"
    assert_eq(status_label("open"), "active")
    assert_eq(status_label("done"), "archived")
    assert_eq(status_label("x"), "unknown")

test "match patterns: literal, enum variant, guard, list, map, wildcard"
    assert_eq(describe(0), "zero")
    assert_eq(describe(Status.paid(5)), "paid 5")
    assert_eq(describe(Status.shipped(2, "DHL")), "express")
    assert_eq(describe(Status.shipped(2, "UPS")), "something else")
    assert_eq(describe([7, 8, 9]), "list of 3 starting at 7")
    assert_eq(describe({"name": "Ana", "age": 20, "city": "x"}), "adult Ana")
    assert_eq(describe({"name": "Ana", "age": 10}), "something else")
    assert_eq(describe({"status": 200, "body": "hi"}), "ok: hi")
    assert_eq(describe("x"), "something else")

test "list patterns: [], [a, b], [...init, last]"
    assert_eq(shape([]), "empty")
    assert_eq(shape([1, 2]), "pair 3")
    assert_eq(shape([1, 2, 3]), "last 3 after 2")

test "a bare name in an `is` arm compares with its value (it does not bind)"
    assert_eq(is_target(3, 3), true)
    assert_eq(is_target(4, 3), false)

test "each accumulates"
    assert_eq(sum_list([1, 2, 3, 4]), 10)

test "each walks a map's keys, text's characters, bytes as integers"
    let ks be []
    each k in {"b": 1, "a": 2}
        set ks to append(ks, k)
    assert_eq(ks, ["b", "a"])
    let cs be []
    each c in "hé"
        set cs to append(cs, c)
    assert_eq(cs, ["h", "é"])
    let bs be []
    each b in bytes([1, 255])
        set bs to append(bs, b)
    assert_eq(bs, [1, 255])

test "stop leaves a loop; there is no continue — wrap the rest in when"
    let seen be []
    each n in [1, 2, 3, 4, 5]
        when n == 4
            stop
        when n != 2
            set seen to append(seen, n)
    assert_eq(seen, [1, 3])

test "each gives a copy of each item: write back by index"
    let rows be [{"p": 1}, {"p": 2}]
    each i in range(length(rows))
        set rows[i]["p"] to rows[i]["p"] * 10
    assert_eq(rows, [{"p": 10}, {"p": 20}])

test "while repeats until the condition is false"
    let n be 0
    while n < 5
        set n to n + 1
    assert_eq(n, 5)

test "try / recover catches; raise re-raises"
    let caught be ""
    try
        raise("boom")
    recover err
        set caught to err
    assert(contains(caught, "boom"))
    assert_error(always_fails)
    assert_error(fails_as_statement)

test "give and stop pass through try untouched"
    assert_eq(give_inside_try(), "given")
    let n be 0
    each x in [1, 2, 3]
        try
            when x == 2
                stop
            set n to n + 1
        recover err
            set n to 100
    assert_eq(n, 1)

task message_of(f)
    try
        f()
    recover err
        give err
    give nothing

task page(limit, offset = 0)
    give limit + offset

test "each walks lists, not arrays: enumerate(a) gives the index and the row (engine v0.6.46+)"
    let total be 0
    each e in enumerate(array([5, 6]))
        set total to total + e.index * 10 + e.item
    assert_eq(total, 21)
    assert(contains(message_of(() => enumerate(array(5))), "0-d array"))

test "messages that name the fix (engine v0.6.46+)"
    assert(contains(message_of(() => log(100)), "ln(x) for the natural log, log10(x), log2(x) or log_base(x, b)"))
    assert(contains(message_of(() => in_units(90, "minutes")), "in_units(duration(seconds = 90), \"minutes\")"))
    assert_eq(in_units(duration(seconds = 90), "minutes"), 1.5)
    assert(contains(message_of(() => page(limt = 5)), "unknown parameter 'limt' — did you mean 'limit'?; the parameters are: limit, offset"))
    assert(contains(message_of(() => sort([3, 1], dsc = true)), "did you mean 'desc'?"))
    assert_eq(page(limit = 5), 5)

Condicionales§

when score >= 90
    give "A"
otherwise when score >= 70
    give "B"
otherwise
    give "C"

Sin dos puntos después de la condición, y sin if/elif/else: esas palabras fallan con la forma Synsema en el mensaje (`elif is not a Synsema statement: chain with otherwise when <condition> `). La forma expresión es when c then a otherwise b` — ver Sintaxis.

match§

match despacha sobre un valor con patrones ricos — literales, variantes de enum, patrones de list/map, guardas y _:

match result
    is "open"                                  -- a value (compared with ==)
        open_it()
    is Status.shipped(days, carrier) when carrier == "DHL"
        express(days)                          -- enum variant + binders + guard
    is [first, ...rest]                        -- list head + tail
        handle(first)
    is {name, age} when age >= 18              -- map keys as binders (extra keys ignored)
        greet(name)
    is {status: 200, body}                     -- map field + binder
        show_ok(body)
    is _                                       -- wildcard
        skip()
    otherwise
        fallback()

Un nombre pelado en un brazo (is target) compara con el valor de esa variable; no bindea. Los binders aparecen solo dentro de patrones de lista, de map y de variante. Los patrones de lista también aceptan [a, b], [] y [...init, last].

Loops§

inserción, sobre texto sus caracteres, sobre bytes enteros 0–255. Cada item es una copia: para cambiar la lista, escribí por índice (set rows[i]["p"] to …) o reconstruila con apply. Para tener el índice junto a cada item, each e in enumerate(xs) → e.index (desde 0) y e.item; desde el engine v0.6.46 enumerate también recibe un array numérico y recorre su primer eje (cada item un número, o una fila). each mismo no recorre un array (Cannot iterate over array): usá enumerate(a) o to_list(a).

asegurate de que la condición cambie.

parser te lo dice si escribís continue).

Preferí las operaciones intencionales (apply/where/reduce) cuando estás transformando una lista.

Errores — try / recover / raise§

try
    risky()
recover err
    log "failed: " + err            -- err is the message (text)
    raise(err)                      -- RE-PROPAGATE; without it, recover swallows the error

raise(mensaje) falla a propósito. give y stop no son errores — pasan por try/recover sin tocarse. (fail(...) es para respuestas HTTP, no para errores de runtime.) Hay un solo tipo de error (no hay tipos de excepción), no hay except/catch ni finally: poné la limpieza después del bloque try/recover.

La forma sentencia raise "msg" / raise err (sin paréntesis) también funciona — desugara a la misma llamada — y un raise solo es un error de parseo ruidoso que apunta al fix. ⚠️ En el v0.5.1 publicado y anteriores, la forma sin paréntesis no hacía nada en silencio (parseaba como dos expresiones inertes); en esos binarios escribí siempre raise("msg").

Mensajes que nombran el arreglo§

Un error dice qué escribir en su lugar, y synsema check avisa varios antes de que el programa corra. Desde el engine v0.6.46:

EscribisteEl mensajeEscribí en su lugar
log(x) (el math.log de Python)`Undefined variable: 'log' — in Synsema: ln(x) for the natural log, log10(x), log2(x) or log_base(x, b); log "message" (no parentheses) writes a log line (synsema check` también lo avisa)ln(x), log10(x), log2(x) o log_base(x, b); log "mensaje" es la sentencia de log
in_units(90, "minutes") (un número)in_units() takes a duration, got number — make one first: in_units(duration(seconds = 90), "minutes")armá primero la duración: in_units(duration(seconds = 90), "minutes") → 1.5
un argumento con nombre que la task o el builtin no tiene: page(limt = 5)unknown parameter 'limt' — did you mean 'limit'?; the parameters are: limit, offset (sin un nombre parecido, solo la lista: unknown parameter 'zzzzzz'; the parameters are: limit, offset)uno de los nombres listados. Antes de v0.6.46 un nombre mal escrito en una task decía que faltaba el correcto

La pista de in_units siempre muestra seconds y "minutes", con tu número: adaptá la unidad que querías. Un argumento con nombre desconocido se detecta cuando la llamada corre — synsema check no lo avisa. Vale igual para lambdas (f(1, c = 2) sobre (a, b) => … lista a, b) y builtins (sort(xs, dsc = true) → did you mean 'desc'?).