Synsemadocsv0.6.xENES

Getting Started

Python → Synsema — the translation table

You (an LLM, or a Python developer) already know Python. This page maps the Python reflex to the Synsema form and flags exactly where the semantics diverge. It is faster than learning from scratch, and it prevents the classic failure mode: writing Python with Synsema keywords.

The one rule that prevents most hallucinations: if you did not see it in these docs, it does not exist. There is no import, no Python stdlib, no classes, no comprehensions, no decorators, no with, no generators, no method-call syntax on values (xs.append(x) → builtins are plain tasks: append(xs, x)).

When a Python reflex slips through anyway, the engine answers in Python terms: the error names the Synsema form to write (return → give, len → length, d.get → get(m, key, default), x = 1 → let/set). The messages quoted below are the engine's own. Since engine v0.6.45 synsema check also catches a call to a name that exists nowhere before the line runs: len(xs) is the warning `there is no task, variable or builtin named 'len' in this file; it fails when that line runs — did you mean length(x)?` (a warning: the exit code does not change).

Every runtime claim below is asserted by this passing doctest. The parse-time hints (a Python keyword or operator that stops the program from loading) cannot run inside a test, so they are quoted from synsema check:

python-diff.syn
-- Doc example: the Python → Synsema divergences that bite the hardest.
-- Every runtime claim in the translation-table page is asserted here (doctested).
-- Parse-time hints (`x = 5`, `return v`, `elif`, `continue`, f"…"…) stop a program from
-- loading, so they cannot live in a test block: the page quotes them from `synsema check`.
intent: "doc example: python-to-synsema translation table"
require env("DOCS_UNSET_VAR")

-- 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 boom()
    raise("kaput")

task reraise_error()
    try
        boom()
    recover err
        raise(err)

task add(a, b)
    give a + b

-- With value semantics a task gets its own copy: the caller sees a change only if it is given back.
task mark(m)
    set m["seen"] to true
    give m

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 "None/True/len/str are undefined names, and the error names the Synsema form"
    assert(contains(error_of(() => None), "in Synsema: nothing"))
    assert(contains(error_of(() => True), "in Synsema: true"))
    assert(contains(error_of(() => len([1])), "in Synsema: length(x)"))
    assert(contains(error_of(() => str(1)), "in Synsema: text(x)"))
    assert(contains(error_of(() => dropna([])), "drop_missing(rows)"))
    assert_eq(nothing == nothing, true)
    assert(is_missing(nothing))

test "no methods: xs.append / d.get fail with the builtin to use"
    let xs be [1]
    let d be {"a": 1}
    assert(contains(error_of(() => xs.append(2)), "set xs to append(xs, item)"))
    assert(contains(error_of(() => d.get("a")), "get(m, key, default)"))

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 short-circuit and always yield a bool"
    let m be {"a": 1}
    assert_eq(contains(m, "b") and m["b"] == 1, false)
    assert_eq(contains(m, "a") and m["a"] == 1, true)
    assert_eq(contains(m, "a") or m["zz"] == 1, true)
    assert_eq(1 or 0, true)

test "truthiness: nothing, false, 0, empty text/list/map are falsy"
    each v in [nothing, false, 0, "", [], {}]
        assert_eq(when v then "t" otherwise "f", "f")
    each v in [1, "a", [0], {"k": 0}]
        assert_eq(when v then "t" otherwise "f", "t")

test "append/insert return a NEW list; 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])
    assert_eq(insert([1, 2, 3], 1, 9), [1, 9, 2, 3])
    assert_eq(insert([1], -1, 0), [0, 1])
    assert(contains(error_of(() => insert([1], 5, 0)), "out of range"))

test "lists and maps are values: a copy never changes behind your back"
    let xs be [1, 2]
    let ys be xs
    set ys[0] to 9
    assert_eq(xs, [1, 2])
    let orig be {"a": 1}
    let changed be mark(orig)
    assert_eq(orig, {"a": 1})
    assert_eq(changed, {"a": 1, "seen": true})
    let rows be [{"p": 1}, {"p": 2}]
    each r in rows
        set r["p"] to 0
    assert_eq(rows, [{"p": 1}, {"p": 2}])
    set rows[0]["p"] to 5
    assert_eq(rows, [{"p": 5}, {"p": 2}])
    let nested be {"a": {}}
    set nested["a"]["b"] to 1
    assert_eq(nested, {"a": {"b": 1}})
    assert_eq(apply((r) => merge(r, {"x": 1}), [{"p": 1}]), [{"p": 1, "x": 1}])

test "strict arity: a missing or an extra argument is an error"
    assert(contains(error_of(() => add(1)), "missing argument 'b'"))
    assert(contains(error_of(() => add(1, 2, 3)), "takes 2 arguments, got 3"))
    assert(contains(error_of(() => append([1], 2, 3)), "takes at most 2 arguments"))
    assert(contains(error_of(() => round(2.567, 2)), "round_to(x, n)"))
    assert_eq(round_to(2.567, 2), 2.57)

test "f-string equivalent is the backtick string; quoted strings stay literal"
    let n be 1
    assert_eq(`n={n + 1}`, "n=2")
    assert_eq(`xs={[1, "a"]} v={nothing}`, "xs=[1, \"a\"] v=nothing")
    assert(contains("n={1+1}", "{"))
    assert(contains(error_of(() => "%d" % 5), "there is no %-formatting"))

test "fmt is strict like str.format: a {name} without a value is an error"
    assert_eq(fmt("{a}-{b}", {"a": 1, "b": 2}), "1-2")
    assert_eq(fmt("{{a}}", {}), "{a}")
    assert(contains(error_of(() => fmt("{a}", {})), "no value for {a}"))

test "\u escapes like Python and JSON; \x is not an escape"
    assert_eq("\u00e9", "é")
    assert_eq("\u{1F600}", "😀")
    assert_eq(length("\uD83D\uDE00"), 1)
    assert_eq(length("\\u00e9"), 6)
    assert_eq(length("C:\users"), 8)
    assert_eq(length("C:\build\x64"), 12)

test "each over a map walks its keys; items gives key and value"
    let ks be []
    each k in {"x": 1, "y": 2}
        set ks to append(ks, k)
    assert_eq(ks, ["x", "y"])
    assert_eq(items({"x": 1}), [{"key": "x", "value": 1}])
    assert_eq(keys({"x": 1}), ["x"])
    assert_eq(values({"x": 1}), [1])
    let cs be []
    each c in "ab"
        set cs to append(cs, c)
    assert_eq(cs, ["a", "b"])
    let bs be []
    each b in bytes([1, 255])
        set bs to append(bs, b)
    assert_eq(bs, [1, 255])

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(apply([1, 2], (x) => x + 1), [2, 3])
    assert_eq(slice([10, 20, 30, 40], 1, 3), [20, 30])
    assert_eq(slice([10, 20, 30, 40], -2), [30, 40])
    assert_eq(reverse("abc"), "cba")
    assert_eq(split("ab", ""), ["a", "b"])

test "negative and text indexes work like Python; an index must be an integer"
    assert_eq([1, 2, 3][-1], 3)
    assert_eq("abc"[0], "a")
    assert_eq("abc"[-1], "c")
    assert_eq([1, 2][1.0], 2)
    assert(contains(error_of(() => [1, 2][1.7]), "index must be an integer"))

test "`in` / `not in`: list item, map KEY, substring; text needs text"
    assert(2 in [1, 2])
    assert(3 not in [1, 2])
    assert("a" in {"a": 1})
    assert("b" in "abc")
    assert(bytes([2, 3]) in bytes([1, 2, 3]))
    assert(contains([1, 2], 2))
    assert(contains(error_of(() => 1 in "a1"), "text(x) in s"))

test "chained comparisons like Python"
    let x be 5
    assert(1 < x <= 10)
    assert_eq(1 < x < 3, false)

test "text + number CONCATENATES; text + nothing/list/map is an error; no text * n"
    assert_eq("a" + 1, "a1")
    assert_eq("a" + true, "atrue")
    assert(contains(error_of(() => "a" + nothing), "Cannot add text and nothing"))
    assert(contains(error_of(() => "a" + [1]), "Cannot add text and list"))
    assert_error(() => "ab" * 2)
    assert_error(() => 1 + true)

test "inside a list or map, text is shown quoted"
    assert_eq(text(["1", 1]), "[\"1\", 1]")
    assert_eq(text("x"), "x")

test "len/str/sorted are length/text/sort; d.get(k, default) is get(m, k, default)"
    assert_eq(length("abc"), 3)
    assert_eq(text(42), "42")
    assert_eq(sort([3, 1, 2]), [1, 2, 3])
    assert_eq(sort([3, 1, 2], desc = true), [3, 2, 1])
    assert_eq(sort([3, nothing, 1]), [1, 3, nothing])
    assert(contains(error_of(() => sort([1, "a"])), "cannot order number and text"))
    assert_eq(sort_by([{"n": 1}, {"n": 3}], (r) => r.n, desc = true), [{"n": 3}, {"n": 1}])
    assert_eq(get({"a": 1}, "a"), 1)
    assert_eq(get({"a": 1}, "b"), nothing)
    assert_eq(get({"a": 1}, "b", 0), 0)
    assert_eq(get([1, 2, 3], -1), 3)
    assert_eq(remove({"a": 1, "b": 2}, "a"), {"b": 2})
    assert_eq(merge({"a": 1, "b": 2}, {"b": 3, "c": 4}), {"a": 1, "b": 3, "c": 4})
    assert(contains(error_of(() => {"a": 1}["b"]), "Map has no key 'b'"))

test "sum/min/max, variadic max, reduce, index_of gives nothing when absent"
    assert_eq(sum([1, 2, 3]), 6)
    assert_eq(min([3, 1]), 1)
    assert_eq(max(1, 5, 2), 5)
    assert_eq(max([1, nothing, 3]), 3)
    assert_eq(max(1, 2,), 2)
    assert_eq(reduce([1, 2, 3], (a, b) => a + b, 0), 6)
    assert_eq(index_of([1, 2], 9), nothing)
    assert_eq(index_of("hello", "l"), 2)
    assert_eq(range(0, 10, 3), [0, 3, 6, 9])
    assert(contains(error_of(() => range(0, 2.5)), "must be an integer"))

test "starts_with / replace / regex: regex_capture is always a list"
    assert(starts_with("hello", "he"))
    assert_eq(replace("a-b", "-", "+"), "a+b")
    assert(matches("abc", "[a-c]+"))
    assert_eq(matches("abcd1", "[a-c]+"), false)
    assert_eq(regex_find_all("a1b2", "[0-9]"), ["1", "2"])
    assert_eq(regex_capture("a1", "([a-z])([0-9])"), ["a", "1"])
    assert_eq(regex_capture("a1", "[0-9]"), ["1"])
    assert_eq(regex_capture("ab", "[0-9]"), nothing)
    assert_eq(regex_replace("a1b2", "[0-9]", "#"), "a#b#")

test "int() is exact; base and fallback go by name"
    assert_eq(int("-42"), -42)
    assert_eq(int("1_000"), 1000)
    assert_eq(int("0x1f"), 31)
    assert_eq(int(3.0), 3)
    assert_eq(int("123456789012345678901"), 123456789012345678901)
    assert_eq(int("ff", base = 16), 255)
    assert_eq(int("abc", fallback = 0), 0)
    assert(contains(error_of(() => int("ff", 16)), "int(x, base = 16)"))
    assert(contains(error_of(() => int("ff", 2.0)), "int(x, base = 2)"))
    assert(contains(error_of(() => int(1.5)), "not a whole number"))
    assert_eq(number("42"), 42.0)
    assert_eq(is_integer(number("42")), false)

test "hex, 0x/0o/0b literals, is_integer/is_text/is_list/is_map"
    assert_eq(hex(7960), "0x1f18")
    assert_eq(hex(bytes([0, 255])), "0x00ff")
    assert_eq(0x1f18, 7960)
    assert_eq(0o17, 15)
    assert_eq(0b101, 5)
    assert(is_integer(1))
    assert_eq(is_integer(1.0), false)
    assert(is_text("a") and is_list([]) and is_map({}))
    assert_eq(type_of(1), "number")

test "// is floor division like Python, exact for big integers; / is always float"
    assert_eq(7 // 2, 3)
    assert_eq(-7 // 2, -4)
    assert_eq(-7 % 3, 2)
    assert_eq((10 ** 30 + 7) // 2, 500000000000000000000000000003)
    assert_eq(7 // 0.1, 69.0)
    assert_eq(10 / 2, 5.0)
    assert_eq(1 / 2, 0.5)

test "-2 ** 2 is -4 and 1e18 is a float, as in Python; big integers compare exactly"
    assert_eq(-2 ** 2, -4)
    assert_eq((-2) ** 2, 4)
    assert_eq(2 ** 3 ** 2, 512)
    assert_eq(is_integer(1e18), false)
    assert_eq(10 ** 18, 1000000000000000000)
    assert_eq(2 ** 53 + 1 == 9007199254740992.0, false)
    assert_eq(json_decode("12345678901234567890"), 12345678901234567890)

test "round is banker's rounding; floor gives an integer"
    assert_eq(round(2.5), 2)
    assert_eq(round(3.5), 4)
    assert_eq(floor(3.7), 3)
    assert(is_integer(floor(3.7)))

test "hmac gives bytes; hex() shows them"
    let mac be hmac("msg", "key")
    assert_eq(type_of(mac), "bytes")
    assert(starts_with(hex(mac), "0x"))

test "enumerate gives {index, item} maps (Python's for i, x in enumerate)"
    let e be enumerate(["a", "b"])
    assert_eq(e[0].index, 0)
    assert_eq(e[1].item, "b")
    assert_eq(length(enumerate([])), 0)

test "pandas reflexes: a table is a list of maps"
    let rows be [{"r": "n", "m": 1}, {"r": "s", "m": 2}, {"r": "n", "m": nothing}]
    assert_eq(summarize(rows, "r", {"total": sum_of("m"), "n": count()}), [{"r": "n", "total": 1, "n": 2}, {"r": "s", "total": 2, "n": 1}])
    assert_eq(group_by(rows, "r")[1], {"key": "s", "items": [{"r": "s", "m": 2}]})
    assert_eq(count_by(rows, "r"), [{"key": "n", "count": 2}, {"key": "s", "count": 1}])
    assert_eq(drop_missing(rows, "m"), [{"r": "n", "m": 1}, {"r": "s", "m": 2}])
    assert_eq(fill_missing(rows, {"m": 0})[2], {"r": "n", "m": 0})
    assert_eq(join([{"id": 1, "a": 1}], [{"id": 1, "b": 2}], "id", "left"), [{"id": 1, "a": 1, "b": 2}])
    assert_eq(collect(rows, "m"), [1, 2, nothing])
    assert_eq(mean([1, nothing, 3]), 2.0)
    assert_eq(round_to(std([1, 2, 3, 4]), 3), 1.291)
    assert_eq(round_to(std([1, 2, 3, 4], ddof = 0), 3), 1.118)
    assert_eq(csv_parse("a,b\n1,\n", {"types": {"a": "int"}}), [{"a": 1, "b": nothing}])
    assert_eq(random_int(rng(42), 1, 6), 1)

test "os.environ.get: env(name, nothing) is nothing when undefined (engine v0.6.45+)"
    assert_eq(env("DOCS_UNSET_VAR", nothing), nothing)
    assert_eq(env("DOCS_UNSET_VAR", true), "true")              -- Synsema text, not Python's "True"
    assert(contains(error_of(() => env("DOCS_UNSET_VAR")), "DOCS_UNSET_VAR"))

Syntax reflexes§

In PythonIn Synsema⚠️ Divergence
x = 5 … x = 6let x be 5 … set x to 6x = 5 → `declare with let x be …, change it with set x to … . = exists ONLY in default params / named args: task f(x, y = 1), f(x, y = 2)`
x += 1set x to x + 1no compound assignment: `Synsema has no compound assignment (+=): write set x to x + 1`
# comment / // comment-- comment# → Unexpected character: '#'. // is integer division, so a C-style comment fails with the hint `comments in Synsema start with -- `
if / elif / else:when / otherwise when / otherwise (no colon)a trailing : → Synsema blocks have no colon; if, elif, else → `is not a Synsema statement with the form to write (chain with otherwise when <condition> , the fallback branch is otherwise `)
x if c else y / c ? x : ywhen c then x otherwise yinline expression form, usable in let/args; both Python and C forms → `the inline form is when c then a otherwise b `
a and b / a && b / a; ba and b / one statement per line&, | → `the logical operators are and / or ; ; → one statement per line (no ;)`
for x in xs:each x in xsfor → parse error
break / continue / passstop / wrap the rest of the body in when / leave the branch outthere is no continue: `skip to the next item by wrapping the rest of the body in when ; break → leave a loop with stop `
d["k"] = v (add or overwrite a key)set m["k"] to vm["k"] reads it, "k" in m tests it. A set path starts from a variable (set m["a"]["b"] to v)
for k in a_dict: / for k, v in d.items():each k in m / each e in items(m) … e.key / e.valueeach over a map walks its keys in insertion order; over text, its characters; over bytes, ints 0–255
for i, x in enumerate(xs):each e in enumerate(xs) … e.index / e.itemenumerate(list) → [{index, item}, …]; engine v0.6.46+ also over a numeric array (its first axis), which each does not walk
while c:while csame keyword, no colon, no iteration cap
def f(x): return vtask f(x) … give vload-time errors that name the Synsema word: `return is not a Synsema statement: give <value> returns from a task`, def is not a Synsema statement: a function is task name(params) …``
f(1) on def f(a, b)a missing or extra argument is an errortask 'f' is missing argument 'b' — pass it, or give the parameter a default, task 'f' takes 2 arguments, got 3; builtins too: append() takes at most 2 arguments, got 3
lambda x: x + 1(x) => x + 1lambda y: y → a lambda is (y) => y
None / True / Falsenothing / true / falsecapitalized forms parse, then fail naming the Synsema form: `Undefined variable: 'None' — in Synsema: nothing`
x is Nonex == nothing / is_missing(x)no is operator for identity (is belongs to match); the parse error says so
os.environ.get("K") / os.getenv("K", "d")env("K", nothing) / env("K", "d") (needs require env("K"))an undefined variable with a nothing default is nothing (engine v0.6.45+; before it was the text "None"); another default comes back as Synsema text: env("K", true) → "true", not "True". Without a default, an undefined variable is an error
f"n={n}"` n={n} ` (backtick string)f"..." → `an f-string is a template with backticks here. A hole takes any value (lists, maps, nothing), like an f-string. Quoted "..." strings do NOT interpolate ("{n}" stays literal) and a literal newline inside them is Unterminated string` — backticks do both
"%d" % x / "{}".format(x)` {x} / fmt("{x}", {"x": x})`% on text → there is no %-formatting: use a template. fmt is strict like str.format: a {name} without a value is an error; {{/}} are literal braces
"""multi-line"""` multi-line `backticks allow real newlines + {expr}
"\u00e9", "\U0001F600""\u00e9", "\u{1F600}"a surrogate pair "\uD83D\uDE00" is one character; for a literal backslash + u write "\\u". \x is not an escape, and an incomplete \u stays as written ("C:\users")
[f(x) for x in xs if p(x)]apply(f, where(xs, p))comprehension syntax → no comprehensions: apply(xs, (v) => …) to transform, where(xs, (v) => …) to filter
xs[-1], s[0]xs[-1], s[0]same: negative indexes count from the end (lists, text, bytes); text is indexed by character. An index must be an integer (xs[1.7] → index must be an integer, got 1.7; xs[1.0] is fine)
xs[1:3], xs[-2:]slice(xs, 1, 3), slice(xs, -2)[1:3] → no slice syntax: slice(xs, start, end?); slice takes Python-style negatives, works on lists/text/bytes
x in xs / x not in xsx in xs / x not in xssame: list item, map key, substring, bytes subsequence. Text needs text: 1 in "a1" → an error (text(1) in "a1"). contains(xs, x) is the function form
a < x <= ba < x <= bchained comparisons like Python; each operand is evaluated once
try/except E as e:try … recover errexcept → `except is not Synsema: after the try block write recover err. err is the message TEXT (no exception types/hierarchy). recover SWALLOWS by default — re-propagate with raise(err)`
raise ValueError("x")raise("x") (or statement raise "x")one error kind only
import json, import requestsnothing to import — builtins are globalimport x → `import is not a Synsema statement: import a local module with use "./file.syn" as name`. JSON/HTTP/etc. are builtins gated by capabilities (below)
from mymodule import fuse "./mymodule.syn" as m … m.f()only local .syn modules; exports need export — Modules
class Person:type Person (fields) + plain tasksno methods/inheritance/self; construct Person("Alice", 30), access p.name / name of p / p["name"]. xs.append(2) → Synsema has no methods: set xs to append(xs, item)
match/casematch … is patternarms use is, default is otherwise — Control flow

Also: the LLM words reason / decide / analyze / generate (like every keyword — to, type, match…) are reserved as names you bind (variables, parameters, tasks): let reason be 1 → 'reason' is a reserved word in Synsema. After a . any word is fine (tx.to, ev.type, mod.decide(…)). Name things resolve, why, etc.

Builtin equivalents (methods are plain tasks)§

In PythonIn Synsema
len(x) / type(x)length(x) (text/list/map/bytes/array) / type_of(x) → "number", "text", …
str(x) / int(s) / float(s)text(x) / int(s) (exact integer: "-42", "1_000", "0x1f", a whole float; int(1.5) errors — floor/round/trunc on purpose) / number(s) (always float)
int(s, 16)int(s, base = 16). The base and the fallback go by name: int(s, fallback = 0) returns 0 instead of raising. int("ff", 16) (any whole number 2..36 in second place, 2.0 included) is an error that names both forms
hex(n) / b.hex()hex(n) → "0x1f18", hex(b) → "0x00ff" (always 0x-prefixed)
isinstance(x, int) / str / list / dictis_integer(x) / is_text(x) / is_list(x) / is_map(x) — is_integer(1.0) is false
a // b / a % ba // b / a % b — floor like Python (-7 // 2 → -4), exact for integers of any size; on floats exactly CPython's result (7 // 0.1 → 69.0)
round(x, 2)round_to(x, 2) — round takes one argument: round(x, 2) → round() takes at most 1 argument, got 2 — to round to n decimals: round_to(x, n)
xs.append(x) (mutates)append(xs, x) → returns a NEW list; reassign: set xs to append(xs, x) (constant time per step, fine in a loop)
xs.insert(i, x)set xs to insert(xs, i, x) — negatives like Python, but an out-of-range i is an error (Python clamps)
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) / s.find(p)starts_with(s, p) / replace(s, a, b) / index_of(s, p)
sorted(xs) / sorted(xs, key=f, reverse=True)sort(xs) / sort_by(xs, f, desc = true) (stable; nothing and NaN go last; mixing numbers and text is an error)
sum(xs) / min(xs) / max(xs)sum(xs) / min(xs) / max(xs) (also variadic max(a, b, c); nothing values are skipped)
map(f, xs) / filter(p, xs)apply(f, xs) / where(xs, p) — both accept either argument order
functools.reduce(f, xs, init)reduce(xs, f, init)
xs.index(v) (raises)index_of(xs, v) → nothing when absent (not -1, no error)
d.get(k) / d.get(k, default)get(m, k) → nothing when missing / get(m, k, default); also lists: get(xs, -1)
d.pop(k) / {a, b}remove(m, k) / merge(a, b) — both return a NEW map (in merge the rightmost value wins)
d.keys() / d.values() / d.items()keys(m) / values(m) / items(m) → [{key, value}, …]
json.dumps(x) / json.loads(s)json_encode(x) / json_decode(s) (pure, no import; big integers stay exact; a secret inside is an error, engine v0.6.46+) — JSON
pd.read_parquet(p, columns=["a"])parquet_scan(p, {"columns": ["a"]}) (engine v0.6.46+; only those pages are read from disk; require file.read(p)) — Data analysis
range(n)range(n) → a real list (also range(a, b, step); integers only)
print(...)print(...) — written immediately; text inside a list or map prints quoted: ["1", 1]
re.fullmatch / re.findall / re.search(p, s).groups() / re.submatches(s, pat) (FULL match) / regex_find_all(s, pat) / regex_capture(s, pat) (always a list — the groups, or [match] without groups — or nothing) / regex_replace(s, pat, rep) — Builtins
hmac.new(k, m, sha256).digest()hmac(m, k) → bytes (hex(mac) to show it)
open(p).read() / requests.get(url)read_file(p) + require file(...) / fetch(url) + require net(host) — Files, HTTP
requests.post(url, json=d) / r.json() / r.contenthttp_post(url, d) (a map → JSON + Content-Type) / json of r (nothing when not JSON) / http_bytes(...) → bytes of r — HTTP
xs[::-1] / list(s)reverse(xs) (also text) / split(s, "")
os.getcwd() / os.path.expanduser("~/x")cwd() + require file.read(".") / "~/x" works as-is in paths and scopes — Files
os.remove(p) / shutil.rmtree(p)delete_file(p) / delete_dir(p, {"recursive": true}) + file.write on every path
zipfile / tarfilezip_create/zip_extract / tar_create/tar_extract — zip-slip rejected, real-bytes ceiling (native)
tomllib.loads(s) / xmltodict.parse(s)toml_parse(s) (dates → ISO text) / xml_parse(s) (same shape) — JSON, XML & TOML

Data analysis — pandas / numpy reflexes§

A table is a list of maps (no DataFrame, no index): every operation takes rows and gives rows. The pandas names themselves (dropna, fillna, groupby, value_counts, read_csv…) fail naming the Synsema builtin (`Undefined variable: 'dropna' — in Synsema: drop_missing(rows)`).

In PythonIn Synsema
df.groupby("r").agg(total=("m", "sum"), n=("m", "size"))summarize(rows, "r", {"total": sum_of("m"), "n": count()}) — groups in first-appearance order
for k, g in df.groupby("r"):each g in group_by(rows, "r") … g.key / g.items — a list of {key, items}, not a dict
df["c"].value_counts()count_by(rows, "c") → [{key, count}]
df.dropna(subset=["m"]) / df.fillna({"m": 0})drop_missing(rows, "m") / fill_missing(rows, {"m": 0}) — missing is nothing
df.merge(o, on="id", how="left")join(rows, o, "id", "left") (join(xs, sep) with two arguments is still the text join)
df["c"] / df[df.x > 2]collect(rows, "c") / where(rows, (r) => r.x > 2)
df.mean() (skips NaN)mean(xs) skips nothing; NaN propagates
df.std() / np.std(xs)std(xs) is the sample figure, like pandas / std(xs, ddof = 0), like numpy
pd.read_csv(p, dtype={"a": int})csv_parse(read_file(p), {"types": {"a": "int"}}) — untyped columns stay text; an empty cell is nothing
np.random.default_rng(42).integers(1, 7)random_int(rng(42), 1, 6) — the same numbers as numpy; the upper bound is inclusive

The full pipeline (CSV, statistics, charts) is in CSV, stats & charts; every table builtin is in Builtins.

Semantic traps — looks like Python, behaves differently§

It looks likeWhat actually happens (doctested above)
a and b short-circuits and returns the operand (x or "default")Short-circuits too but always returns a bool — x or "default" is true/false, never the default. Use when x == nothing … set x to "default", or get(m, k, default) for a map
xs.append mutates in placeappend (and friends) return new values; the original is untouched. Reassign with set
Two names for one list/dict see each other's changesLists and maps are values: let ys be xs + set ys[0] to 9 leaves xs alone, and a task that sets inside a map it received does NOT change the caller's — give it back and set it in the caller
for r in rows: r["x"] = 1 updates the rowseach r in rows + set r["x"] to 1 changes the loop's copy (synsema check warns). Write set rows to apply((r) => merge(r, {"x": 1}), rows), or by index: set rows[i]["x"] to 1
d["missing"] → KeyError you catch by typeMap has no key 'missing' — catchable only as try/recover (message text). For an optional key, get(m, "missing", default)
"a" + 1 → TypeErrorIt concatenates: "a" + 1 → "a1" (text + number/bool). But text + nothing/list/map/bytes IS an error (Cannot add text and nothing — convert it on purpose: text(x), or interpolate it), and so are "ab" * 2 and 1 + true — no repetition, no bool arithmetic
except: keeps the program dyingrecover swallows the error entirely (task ends normally). To fail upward, raise(err) inside recover

More traps (also engine-verified): Counter your priors and Errors & exit codes.

Where Python intuition is SAFE (verified — trust it)§

No Python equivalent — read the topic page before using§

db(...) / serve(PORT) / llm at the top or calls fail → Capabilities

parallel_map → Multi-agent