Python → Synsema — la tabla de traducción
Vos (un LLM, o un dev de Python) ya sabés Python. Esta página mapea el reflejo de Python a
la forma Synsema y marca exactamente dónde diverge la semántica. Es más rápido que aprender
de cero, y previene el modo de falla clásico: escribir Python con keywords de Synsema.
**La regla que previene la mayoría de las alucinaciones: si no lo viste en estas docs, no
existe.** No hay import, no hay stdlib de Python, no hay clases, no hay comprehensions,
no hay decoradores, no hay with, no hay generadores, no hay sintaxis de método sobre
valores (xs.append(x) → los builtins son tasks planas: append(xs, x)).
Cada afirmación semántica de abajo está asegurada por este doctest que pasa:
-- Doc example: the Python → Synsema divergences that bite the hardest.
-- Every claim in the translation-table page is asserted here (doctested).
intent: "doc example: python-to-synsema translation table"
task boom()
raise("kaput")
task reraise_error()
try
boom()
recover err
raise(err)
task bad_return()
return 5
task give_none()
give None
task iterate_map_directly()
each k in {"a": 1}
print(k)
test "assignment is let/set, not = (x = 5 is a parse error)"
let x be 1
set x to 2
assert_eq(x, 2)
test "Python 'return'/'None'/'True' PARSE but are undefined names — use give/nothing/true"
assert_error(bad_return)
assert_error(give_none)
assert_eq(nothing == nothing, true)
test "try/recover (not try/except); recover SWALLOWS unless you raise(err)"
let seen be ""
try
boom()
recover err
set seen to err
assert(contains(seen, "kaput"))
assert_error(reraise_error)
test "and/or do NOT short-circuit — guard with nested when, not with `and`"
let m be {"a": 1}
assert_error(() => contains(m, "b") and m["b"] == 1)
when contains(m, "a")
assert_eq(m["a"], 1)
test "append returns a NEW list (no mutating .append); reassign with set"
let xs be [1, 2]
let ys be append(xs, 3)
assert_eq(xs, [1, 2])
assert_eq(ys, [1, 2, 3])
test "f-string equivalent is the backtick string; quoted strings stay literal"
assert_eq(`n={1 + 1}`, "n=2")
assert(contains("n={1+1}", "{"))
test "each cannot iterate a map — go through keys()"
assert_error(iterate_map_directly)
let ks be []
each k in keys({"x": 1, "y": 2})
set ks to append(ks, k)
assert_eq(ks, ["x", "y"])
test "comprehension equivalent: apply + where; slicing is slice()"
assert_eq(apply((x) => x * 2, where([1, 2, 3], (x) => x > 1)), [4, 6])
assert_eq(slice([10, 20, 30, 40], 1, 3), [20, 30])
assert_eq(slice([10, 20, 30, 40], -2, 4), [30, 40])
test "the `in` operator is contains(); on maps it checks KEYS"
assert(contains([1, 2], 2))
assert(contains("abc", "b"))
assert(contains({"a": 1}, "a"))
test "text + number CONCATENATES (unlike Python); text * n does not exist"
assert_eq("a" + 1, "a1")
assert_error(() => "ab" * 2)
test "len/str/sorted are length/text/sort_by; d.get(k, default) does not exist"
assert_eq(length("abc"), 3)
assert_eq(text(42), "42")
assert_eq(sort_by([3, 1, 2], (x) => x), [1, 2, 3])
assert_error(() => get({"a": 1}, "a"))
assert_error(() => {"a": 1}["b"])
Reflejos de sintaxis
| En Python | En Synsema | ⚠️ Divergencia |
|---|---|---|
x = 5 … x = 6 | let x be 5 … set x to 6 | x = 5 → error de parseo Unexpected token: ASSIGN ('='). = existe SOLO en params con default / args nombrados: task f(x, y = 1), f(x, y = 2) |
# comentario | -- comentario | # → Unexpected character: '#' |
if / elif / else: | when / otherwise when / otherwise (sin dos puntos) | un : al final → error de parseo; elif no es una palabra |
x if c else y | when c then x otherwise y | forma expresión inline, usable en let/args |
for x in xs: | each x in xs | for → error de parseo |
for k in un_dict: | each k in keys(m) | each no puede iterar un map: Cannot iterate over map. Pasá por keys(m)/values(m) |
for i, x in enumerate(xs): | each i in range(length(xs)) … xs[i] | no hay enumerate |
while c: | while c | mismo keyword, sin dos puntos; un loop desbocado corta con Loop exceeded maximum iterations |
def f(x): return v | task f(x) … give v | def → error de parseo. return PARSEA como un nombre y falla en runtime: Undefined variable: 'return' — la palabra es give |
lambda x: x + 1 | (x) => x + 1 | — |
None / True / False | nothing / true / false | las formas con mayúscula parsean y después fallan: Undefined variable: 'None' (igual True/False) |
x is None | x == nothing | no hay operador is de identidad (is es de match) |
f"n={n}" | ` n={n} ` (string con backticks) | f"..." → error de parseo. Las strings con comillas "..." NO interpolan ("{n}" queda literal) y un newline literal adentro es Unterminated string — los backticks hacen ambas cosas |
"""multilínea""" | ` multilínea ` | los backticks permiten newlines reales + {expr} |
[f(x) for x in xs if p(x)] | apply(f, where(xs, p)) | la sintaxis de comprehension → error de parseo |
xs[1:3], xs[-2:] | slice(xs, 1, 3), slice(xs, -2, length(xs)) | [1:3] → error de parseo; slice acepta negativos estilo Python, funciona en listas/text/bytes |
x in xs (operador) | contains(xs, x) | in solo vale dentro de each. En maps contains chequea CLAVES |
try/except E as e: | try … recover err | except → error de parseo. err es el TEXTO del mensaje (no hay tipos/jerarquía de excepciones). recover TRAGA por defecto — re-propagá con raise(err) |
raise ValueError("x") | raise("x") (o la sentencia raise "x") | un solo tipo de error; en engine ≤ v0.5.1 usá la forma con paréntesis |
import json, import requests | nada que importar — los builtins son globales | import x parsea como un nombre y falla: Undefined variable: 'import'. JSON/HTTP/etc. son builtins gateados por capabilities (abajo) |
from mimodulo import f | use "./mimodulo.syn" as m … m.f() | solo módulos .syn locales; los exports necesitan export — Módulos |
class Person: | type Person (campos) + tasks planas | sin métodos/herencia/self; construí Person("Alice", 30), accedé p.name / name of p / p["name"] |
match/case | match … is patrón | los brazos usan is, el default es otherwise — Sintaxis |
Además: las palabras LLM **reason / decide / analyze / generate son reservadas en
todas partes** (incluso como nombres de member/param) — let reason be 1 → `'reason' is a
reserved word in Synsema. Nombrá las cosas resolve, why`, etc.
Equivalentes de builtins (los métodos son tasks planas)
| En Python | En Synsema |
|---|---|
len(x) | length(x) (text/list/map/bytes/array) |
str(x) / int(s) / float(s) | text(x) / number(s) (siempre float; floor() para entero) |
xs.append(x) (muta) | append(xs, x) → devuelve una lista NUEVA; reasigná: set xs to append(xs, x) |
s.upper() / s.lower() / s.strip() | upper(s) / lower(s) / trim(s) |
s.split(",") / ",".join(xs) | split(s, ",") / join(xs, ",") |
s.startswith(p) / s.replace(a, b) | starts_with(s, p) / replace_text(s, a, b) |
sorted(xs, key=f) / reverse=True | sort_by(xs, f) / sort_by(xs, (x) => 0 - x) (no hay sort pelado) |
sum(xs) / min(xs) / max(xs) | sum(xs) / min(xs) / max(xs) (también variádico max(a, b, c)) |
map(f, xs) / filter(p, xs) | apply(f, xs) / where(xs, p) — ambos aceptan cualquiera de los dos órdenes |
functools.reduce(f, xs, init) | reduce(xs, f, init) |
xs.index(v) (lanza) / v in xs | index_of(xs, v) → nothing si no está (no -1, no error) |
d.get(k, default) | no existe — when contains(m, "k") y después indexá (when anidado, ver trampas) |
d.keys() / d.values() / d.items() | keys(m) / values(m) / no hay items — iterá keys(m) e indexá |
json.dumps(x) / json.loads(s) | json_encode(x) / json_decode(s) (puros, sin import) — JSON |
range(n) | range(n) → una lista real (también range(a, b, paso)) |
print(...) | print(...) (bufferea bajo run hasta salir — flush() para salida en vivo) |
re.fullmatch / re.findall | matches(s, pat) (match COMPLETO) / find_all(s, pat) — Builtins |
open(p).read() / requests.get(url) | read_file(p) + require file(...) / fetch(url) + require net(host) — Archivos, HTTP |
Trampas semánticas — parece Python, se comporta distinto
| Parece que | Lo que pasa en realidad (doctesteado arriba) |
|---|---|
a and b cortocircuita | NO cortocircuita — ambos lados se evalúan SIEMPRE. contains(m, "k") and m["k"] == 1 igual falla con Map has no key 'k' cuando la clave no está. Guardá con when anidado |
xs.append muta in place | append (y familia) devuelven valores nuevos; el original queda intacto. Reasigná con set |
d["missing"] → KeyError que atrapás por tipo | Map has no key 'missing' — atrapable solo como try/recover (texto del mensaje) |
"a" + 1 → TypeError | Concatena: "a" + 1 → "a1" (text + number coerciona). Pero "ab" * 2 y 1 + true SÍ son errores — no hay repetición ni aritmética de bools |
except: deja el programa muriendo | recover traga el error entero (la task termina normal). Para fallar hacia arriba, raise(err) dentro de recover |
| iterar un dict da las claves | each sobre un map es ERROR — usá keys(m) |
Más trampas (también verificadas contra el engine): Contra tus instintos
Dónde tu intuición de Python SÍ es segura (verificado — confiá)
- La división siempre devuelve float (
10 / 3→3.33…), como Python 3. División entera:floor(a / b). round()es redondeo banker's, igual que Python:round(2.5)→2,round(3.5)→4.- Truthiness:
nothing/false/0/""/[]/{}son falsy, todo lo demás truthy. [1] + [2]→[1, 2](concatenación de listas),sliceacepta índices negativos.- Los literales de map
{"k": v}y de lista se ven y anidan como dicts/listas. - La indentación define bloques (4 espacios), comentarios hasta fin de línea,
and/or/notson palabras.
Sin equivalente en Python — leé la página del tema antes de usar
- Capabilities: la I/O es deny-by-default; declará
require net("host")/file(...)/
db(...) / serve(PUERTO) / llm arriba o las llamadas fallan → Capabilities
- Ops LLM como keywords:
decide between [...] given x,generate,analyze,reason→ Primitivas LLM - Agentes/concurrencia:
agent/spawn/share/observe/signal/wait_for,
parallel_map → Multi-agente
- Servidor HTTP como sintaxis:
serve on 8080+ bloquesroute "GET /x"→ Serve - Secretos: valores
secret("KEY")que jamás se imprimen/serializan → Secretos - Humano en el loop:
approve/confirm/ask/show→ Humano en el loop - Tests en el archivo: bloques
test "nombre"+assert_eq, corridos porsynsema test→ CLI