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)).
Every semantic claim below is asserted by this passing doctest:
-- 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"])
Syntax reflexes
| In Python | In Synsema | ⚠️ Divergence |
|---|---|---|
x = 5 … x = 6 | let x be 5 … set x to 6 | x = 5 → parse error Unexpected token: ASSIGN ('='). = exists ONLY in default params / named args: task f(x, y = 1), f(x, y = 2) |
# comment | -- comment | # → Unexpected character: '#' |
if / elif / else: | when / otherwise when / otherwise (no colon) | a trailing : → parse error; elif is not a word |
x if c else y | when c then x otherwise y | inline expression form, usable in let/args |
for x in xs: | each x in xs | for → parse error |
for k in a_dict: | each k in keys(m) | each cannot iterate a map: Cannot iterate over map. Go through keys(m)/values(m) |
for i, x in enumerate(xs): | each i in range(length(xs)) … xs[i] | no enumerate |
while c: | while c | same keyword, no colon; runaway loops hit Loop exceeded maximum iterations |
def f(x): return v | task f(x) … give v | def → parse error. return PARSES as a plain name, then fails at runtime: Undefined variable: 'return' — the word is give |
lambda x: x + 1 | (x) => x + 1 | — |
None / True / False | nothing / true / false | capitalized forms parse, then fail: Undefined variable: 'None' (same for True/False) |
x is None | x == nothing | no is operator for identity (is belongs to match) |
f"n={n}" | ` n={n} ` (backtick string) | f"..." → parse error. Quoted "..." strings do NOT interpolate ("{n}" stays literal) and a literal newline inside them is Unterminated string — backticks do both |
"""multi-line""" | ` multi-line ` | backticks allow real newlines + {expr} |
[f(x) for x in xs if p(x)] | apply(f, where(xs, p)) | comprehension syntax → parse error |
xs[1:3], xs[-2:] | slice(xs, 1, 3), slice(xs, -2, length(xs)) | [1:3] → parse error; slice takes Python-style negatives, works on lists/text/bytes |
x in xs (operator) | contains(xs, x) | in is only valid inside each. On maps contains checks KEYS |
try/except E as e: | try … recover err | except → parse error. 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; on engine ≤ v0.5.1 use the parens form |
import json, import requests | nothing to import — builtins are global | import x parses as a name and fails: Undefined variable: 'import'. JSON/HTTP/etc. are builtins gated by capabilities (below) |
from mymodule import f | use "./mymodule.syn" as m … m.f() | only local .syn modules; exports need export — Modules |
class Person: | type Person (fields) + plain tasks | no methods/inheritance/self; construct Person("Alice", 30), access p.name / name of p / p["name"] |
match/case | match … is pattern | arms use is, default is otherwise — Syntax |
Also: the LLM words **reason / decide / analyze / generate are reserved
everywhere** (even as member/param names) — let reason be 1 → `'reason' is a reserved
word in Synsema. Name things resolve, why`, etc.
Builtin equivalents (methods are plain tasks)
| In Python | In Synsema |
|---|---|
len(x) | length(x) (text/list/map/bytes/array) |
str(x) / int(s) / float(s) | text(x) / number(s) (always float; floor() to get an integer) |
xs.append(x) (mutates) | append(xs, x) → returns a NEW list; reassign: 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 bare sort) |
sum(xs) / min(xs) / max(xs) | sum(xs) / min(xs) / max(xs) (also variadic max(a, b, c)) |
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) / v in xs | index_of(xs, v) → nothing when absent (not -1, no error) |
d.get(k, default) | does not exist — when contains(m, "k") then index (nested when, see traps) |
d.keys() / d.values() / d.items() | keys(m) / values(m) / no items — iterate keys(m) and index |
json.dumps(x) / json.loads(s) | json_encode(x) / json_decode(s) (pure, no import) — JSON |
range(n) | range(n) → a real list (also range(a, b, step)) |
print(...) | print(...) (buffered under run until exit — flush() for live output) |
re.fullmatch / re.findall | matches(s, pat) (FULL match) / find_all(s, pat) — Builtins |
open(p).read() / requests.get(url) | read_file(p) + require file(...) / fetch(url) + require net(host) — Files, HTTP |
Semantic traps — looks like Python, behaves differently
| It looks like | What actually happens (doctested above) |
|---|---|
a and b short-circuits | NO short-circuit — both sides ALWAYS evaluate. contains(m, "k") and m["k"] == 1 still errors Map has no key 'k' when the key is absent. Guard with nested when instead |
xs.append mutates in place | append (and friends) return new values; the original is untouched. Reassign with set |
d["missing"] → KeyError you catch by type | Map has no key 'missing' — catchable only as try/recover (message text) |
"a" + 1 → TypeError | It concatenates: "a" + 1 → "a1" (text + number coerces). But "ab" * 2 and 1 + true ARE errors — no repetition, no bool arithmetic |
except: keeps the program dying | recover swallows the error entirely (task ends normally). To fail upward, raise(err) inside recover |
| iterating a dict yields keys | each over a map is an ERROR — use keys(m) |
More traps (also engine-verified): Counter your priors
and Errors & exit codes.
Where Python intuition is SAFE (verified — trust it)
- Division always returns float (
10 / 3→3.33…), like Python 3. Floor-div:floor(a / b). round()is banker's rounding, same as Python:round(2.5)→2,round(3.5)→4.- Truthiness:
nothing/false/0/""/[]/{}are falsy, everything else truthy. [1] + [2]→[1, 2](list concatenation),sliceaccepts negative indices.- Map literals
{"k": v}and list literals look and nest like dicts/lists. - Indentation defines blocks (4 spaces), comments to end of line,
and/or/notare words.
No Python equivalent — read the topic page before using
- Capabilities: I/O is deny-by-default; declare
require net("host")/file(...)/
db(...) / serve(PORT) / llm at the top or calls fail → Capabilities
- LLM ops as keywords:
decide between [...] given x,generate,analyze,reason→ LLM primitives - Agents/concurrency:
agent/spawn/share/observe/signal/wait_for,
parallel_map → Multi-agent
- HTTP server as syntax:
serve on 8080+route "GET /x"blocks → Serve - Secrets:
secret("KEY")values that never print/serialize → Secrets - Human-in-the-loop:
approve/confirm/ask/show→ Human-in-the-loop - Tests in-file:
test "name"blocks +assert_eq, run bysynsema test→ CLI