Lenguaje
Control de flujo y errores
-- 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§
each item in collectionitera una lista; sobre un map recorre las claves en orden de
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).
while conditionrepite hasta que la condición es falsa. No hay tope de iteraciones:
asegurate de que la condición cambie.
stopsale del loop. No haycontinue— envolvé el resto del cuerpo enwhen(el
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:
| Escribiste | El mensaje | Escribí 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'?).