From 79025b991371f40ed9542dc2b5a2e6063de3522e Mon Sep 17 00:00:00 2001 From: giles Date: Sat, 7 Mar 2026 12:08:14 +0000 Subject: [PATCH] =?UTF-8?q?New=20essay:=20There=20Is=20No=20Alternative=20?= =?UTF-8?q?=E2=80=94=20why=20s-expressions=20are=20the=20only=20option?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Systematic examination of XML, JSON, YAML, JSX, Tcl, Rebol, and Forth against the six roles SX requires (markup, language, wire format, data notation, spec language, metaprogramming). Comparison table across five properties. Every candidate either fails requirements or converges toward s-expressions under a different name. Co-Authored-By: Claude Opus 4.6 --- sx/sx/essays.sx | 199 +++++++++++++++++++++++++++++++++++++++++++ sx/sx/nav-data.sx | 2 + sx/sxc/pages/docs.sx | 1 + 3 files changed, 202 insertions(+) diff --git a/sx/sx/essays.sx b/sx/sx/essays.sx index 0b8d9d1..d92fd94 100644 --- a/sx/sx/essays.sx +++ b/sx/sx/essays.sx @@ -478,3 +478,202 @@ "Not because they were designed for AI. " (a :href "https://en.wikipedia.org/wiki/John_McCarthy_(computer_scientist)" :class "text-violet-600 hover:underline" "McCarthy") " invented them in 1958, decades before anyone imagined language models. But the properties that make s-expressions elegant for humans — minimalism, uniformity, composability, homoiconicity — turn out to be exactly the properties that make them tractable for machines. The simplest possible syntax is also the most machine-friendly syntax. This is not a coincidence. It is a consequence of what simplicity means.") (p :class "text-stone-600" "The era of AI-generated software is not coming. It is here. The question is which representations survive contact with it. The ones with the lowest syntax tax, the most uniform structure, and the tightest feedback loops will win — not because they are trendy, but because they are what the machines can actually produce reliably. S-expressions have been waiting sixty-seven years for a generation mechanism worthy of their simplicity. They finally have one.")))) + +(defcomp ~essay-no-alternative () + (~doc-page :title "There Is No Alternative" + (p :class "text-stone-500 text-sm italic mb-8" + "Every attempt to escape s-expressions leads back to s-expressions. This is not an accident.") + + (~doc-section :title "The claim" :id "claim" + (p :class "text-stone-600" + "SX uses s-expressions. When people encounter this, the first reaction is usually: " (em "why not use something more modern?") " Fair question. The answer is that there is nothing more modern. There are only things that are more " (em "familiar") " — and familiarity is not the same as fitness.") + (p :class "text-stone-600" + "This essay examines what SX actually needs from its representation, surveys the alternatives, and shows that every candidate either fails to meet the requirements or converges toward s-expressions under a different name. The conclusion is uncomfortable but unavoidable: for what SX does, there is no alternative.")) + + (~doc-section :title "The requirements" :id "requirements" + (p :class "text-stone-600" + "SX is not just a templating language. It is a language that serves simultaneously as:") + (ul :class "space-y-2 text-stone-600" + (li (strong "Markup") " — structure for HTML pages, components, layouts") + (li (strong "Programming language") " — conditionals, iteration, functions, macros, closures") + (li (strong "Wire format") " — what the server sends to the client over HTTP") + (li (strong "Data notation") " — configuration, page definitions, component registries") + (li (strong "Spec language") " — the SX specification is written in SX") + (li (strong "Metaprogramming substrate") " — macros that read, transform, and generate code")) + (p :class "text-stone-600" + "Any replacement must handle all six roles with a single syntax. Not six syntaxes awkwardly interleaved — one. This constraint alone eliminates most candidates, because most representations were designed for one of these roles and are ill-suited to the others.") + (p :class "text-stone-600" + "Beyond versatility, the representation must be:") + (ul :class "space-y-2 text-stone-600" + (li (strong "Homoiconic") " — code must be data and data must be code, because macros and self-hosting require it") + (li (strong "Parseable in one pass") " — no forward references, no context-dependent grammar, because the wire format must be parseable by a minimal client") + (li (strong "Structurally validatable") " — a syntactically valid expression must be checkable without evaluation, because untrusted code from federated nodes must be validated before execution") + (li (strong "Token-efficient") " — minimal syntactic overhead, because the representation travels over the network and is processed by LLMs with finite context windows") + (li (strong "Composable by nesting") " — no special composition mechanisms, because the same operation (putting a list inside a list) must work for markup, logic, and data"))) + + (~doc-section :title "The candidates" :id "candidates" + + (~doc-subsection :title "XML / HTML" + (p :class "text-stone-600" + "The obvious first thought. XML is a tree. HTML is markup. Why not use angle brackets?") + (p :class "text-stone-600" + "XML fails on homoiconicity. The distinction between elements, attributes, text nodes, processing instructions, CDATA sections, entity references, and namespaces means the representation has multiple structural categories that cannot freely substitute for each other. An attribute is not an element. A text node is not a processing instruction. You cannot take an arbitrary XML fragment and use it as code, because XML has no concept of evaluation — it is a serialization format for trees, not a language.") + (p :class "text-stone-600" + "XML also fails on token efficiency. " (code "

Title

") " versus " (code "(div :class \"card\" (h2 \"Title\"))") ". The closing tags carry zero information — they are pure redundancy. Over a full application, this redundancy compounds into significantly more bytes on the wire and significantly more tokens in an LLM context window.") + (p :class "text-stone-600" + "XSLT attempted to make XML a programming language. The result is universally regarded as a cautionary tale. Trying to express conditionals and iteration in a format designed for document markup produces something that is bad at both.")) + + (~doc-subsection :title "JSON" + (p :class "text-stone-600" + "JSON is data notation. It has objects, arrays, strings, numbers, booleans, and null. It parses in one pass. It validates structurally. It is ubiquitous.") + (p :class "text-stone-600" + "JSON is not homoiconic because it has no concept of evaluation. It is " (em "inert") " data. To make JSON a programming language, you must invent a convention for representing code — and every such convention reinvents s-expressions with worse ergonomics:") + (~doc-code :code (highlight ";; JSON \"code\" (actual example from various JSON-based DSLs)\n{\"if\": [{\">\": [\"$.count\", 0]},\n {\"map\": [\"$.items\", {\"fn\": [\"item\", {\"get\": [\"item\", \"name\"]}]}]},\n {\"literal\": \"No items\"}]}\n\n;; The same thing in s-expressions\n(if (> count 0)\n (map (fn (item) (get item \"name\")) items)\n \"No items\")" "lisp")) + (p :class "text-stone-600" + "The JSON version is an s-expression encoded in JSON's syntax — lists-of-lists with a head element that determines semantics. It has strictly more punctuation (colons, commas, braces, brackets, quotes around keys) and strictly less readability. Every JSON-based DSL that reaches sufficient complexity converges on this pattern and then wishes it had just used s-expressions.")) + + (~doc-subsection :title "YAML" + (p :class "text-stone-600" + "YAML is the other common data notation. It adds indentation sensitivity, anchors, aliases, multi-line strings, type coercion, and a " (a :href "https://yaml.org/spec/1.2.2/" :class "text-violet-600 hover:underline" "specification") " that is 240 pages long. The spec for SX's parser is 200 lines.") + (p :class "text-stone-600" + "Indentation sensitivity is a direct disqualifier for wire formats. Whitespace must survive serialization, transmission, minification, and reconstruction exactly — a fragility that s-expressions do not have. YAML also fails on structural validation: the " (a :href "https://en.wikipedia.org/wiki/Norway_problem" :class "text-violet-600 hover:underline" "Norway problem") " (" (code "NO") " parsed as boolean " (code "false") ") demonstrates that YAML's type coercion makes structural validation impossible without semantic knowledge of the schema.") + (p :class "text-stone-600" + "YAML is not homoiconic. It has no evaluation model. Like JSON, any attempt to encode logic in YAML produces s-expressions with worse syntax.")) + + (~doc-subsection :title "JSX / Template literals" + (p :class "text-stone-600" + "JSX is the closest mainstream technology to what SX does — it embeds markup in a programming language. But JSX is not a representation; it is a compile target. " (code "content") " compiles to " (code "React.createElement(Card, {title: \"Hi\"}, \"content\")") ". The angle-bracket syntax is sugar that does not survive to runtime.") + (p :class "text-stone-600" + "This means JSX cannot be a wire format — the client must have the compiler. It cannot be a spec language — you cannot write a JSX spec in JSX without a build step. It cannot be a data notation — it requires JavaScript evaluation context. JSX handles exactly one of the six roles (markup) and delegates the others to JavaScript, CSS, JSON, and whatever build tool assembles them.") + (p :class "text-stone-600" + "Template literals (tagged templates in JavaScript, Jinja, ERB, etc.) are string interpolation. They embed code in strings or strings in code, depending on which layer you consider primary. Neither direction produces a homoiconic representation. You cannot write a macro that reads a template literal and transforms it as data, because the template literal is a string — opaque, uninspectable, and unstructured.")) + + (~doc-subsection :title "Tcl" + (p :class "text-stone-600" + "Tcl is the most interesting near-miss. \"Everything is a string\" is a radical simplification. The syntax is minimal: commands are words separated by spaces, braces group without substitution, brackets evaluate. Tcl is effectively homoiconic — code is strings, strings are code, and " (code "eval") " is the universal mechanism.") + (p :class "text-stone-600" + "Where Tcl falls short is structural validation. Because everything is a string, you cannot check that a Tcl program is well-formed without evaluating it. Unmatched braces inside string values are indistinguishable from syntax errors without context. S-expressions have a trivial structural check — balanced parentheses — that requires no evaluation and no context. For sandboxed evaluation of untrusted code (federated expressions from other nodes), this difference is decisive.") + (p :class "text-stone-600" + "Tcl also lacks native tree structure. Lists are flat strings that are parsed on demand. Nested structure exists by convention, not by grammar. This makes composition more fragile than s-expressions, where nesting is the fundamental structural primitive.")) + + (~doc-subsection :title "Rebol / Red" + (p :class "text-stone-600" + "Rebol is the strongest alternative. It is homoiconic — code is data. It has minimal syntax. It has dialecting — the ability to create domain-specific languages within the language. It is a single representation for code, data, and markup. " (a :href "https://en.wikipedia.org/wiki/Rebol" :class "text-violet-600 hover:underline" "Carl Sassenrath") " designed it explicitly to solve the problems that SX also targets.") + (p :class "text-stone-600" + "Rebol's limitation is practical, not theoretical. The language is obscure. Modern AI models have almost no training data for it — generating reliable Rebol would require extensive fine-tuning or few-shot prompting. There is no ecosystem of libraries, no community producing components, no federation protocol. And Rebol's type system (over 40 built-in datatypes including " (code "url!") ", " (code "email!") ", " (code "money!") ") makes the parser substantially more complex than s-expressions, which have essentially one composite type: the list.") + (p :class "text-stone-600" + "Rebol demonstrates that the design space around s-expressions has room for variation. But the variations add complexity without adding expressiveness — and in the current landscape, complexity kills AI compatibility and adoption equally.")) + + (~doc-subsection :title "Forth / stack-based" + (p :class "text-stone-600" + "Forth has the most minimal syntax imaginable: words separated by spaces. No parentheses, no brackets, no delimiters. The program is a flat sequence of tokens. This is simpler than s-expressions.") + (p :class "text-stone-600" + "But Forth's simplicity is deceptive. The flat token stream encodes tree structure " (em "implicitly") " via stack effects. Understanding what a Forth program does requires mentally simulating the stack — tracking what each word pushes and pops. This is precisely the kind of implicit state tracking that both humans and AI models struggle with. A nested s-expression makes structure " (em "visible") ". A Forth program hides it.") + (p :class "text-stone-600" + "For markup, this is fatal. " (code "3 1 + 4 * 2 /") " is arithmetic. Now imagine a page layout expressed as stack operations. The nesting that makes " (code "(div (h2 \"Title\") (p \"Body\"))") " self-evident becomes an exercise in mental bookkeeping. UI is trees. Stack languages are not."))) + + (~doc-section :title "The convergence" :id "convergence" + (p :class "text-stone-600" + "Every alternative either fails to meet the requirements or reinvents s-expressions:") + (div :class "overflow-x-auto rounded border border-stone-200 mb-4" + (table :class "w-full text-left text-sm" + (thead (tr :class "border-b border-stone-200 bg-stone-100" + (th :class "px-3 py-2 font-medium text-stone-600" "Candidate") + (th :class "px-3 py-2 font-medium text-stone-600" "Homoiconic") + (th :class "px-3 py-2 font-medium text-stone-600" "Structural validation") + (th :class "px-3 py-2 font-medium text-stone-600" "Token-efficient") + (th :class "px-3 py-2 font-medium text-stone-600" "Tree-native") + (th :class "px-3 py-2 font-medium text-stone-600" "AI-trainable"))) + (tbody + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" "XML/HTML") + (td :class "px-3 py-2 text-red-600" "No") + (td :class "px-3 py-2 text-green-700" "Yes") + (td :class "px-3 py-2 text-red-600" "No") + (td :class "px-3 py-2 text-green-700" "Yes") + (td :class "px-3 py-2 text-green-700" "Yes")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" "JSON") + (td :class "px-3 py-2 text-red-600" "No") + (td :class "px-3 py-2 text-green-700" "Yes") + (td :class "px-3 py-2 text-red-600" "No") + (td :class "px-3 py-2 text-green-700" "Yes") + (td :class "px-3 py-2 text-green-700" "Yes")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" "YAML") + (td :class "px-3 py-2 text-red-600" "No") + (td :class "px-3 py-2 text-red-600" "No") + (td :class "px-3 py-2 text-amber-600" "Moderate") + (td :class "px-3 py-2 text-red-600" "No") + (td :class "px-3 py-2 text-green-700" "Yes")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" "JSX") + (td :class "px-3 py-2 text-red-600" "No") + (td :class "px-3 py-2 text-red-600" "Needs compiler") + (td :class "px-3 py-2 text-amber-600" "Moderate") + (td :class "px-3 py-2 text-green-700" "Yes") + (td :class "px-3 py-2 text-green-700" "Yes")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" "Tcl") + (td :class "px-3 py-2 text-green-700" "Yes") + (td :class "px-3 py-2 text-red-600" "No") + (td :class "px-3 py-2 text-green-700" "Yes") + (td :class "px-3 py-2 text-red-600" "No") + (td :class "px-3 py-2 text-red-600" "No")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" "Rebol/Red") + (td :class "px-3 py-2 text-green-700" "Yes") + (td :class "px-3 py-2 text-amber-600" "Complex") + (td :class "px-3 py-2 text-green-700" "Yes") + (td :class "px-3 py-2 text-green-700" "Yes") + (td :class "px-3 py-2 text-red-600" "No")) + (tr :class "border-b border-stone-100" + (td :class "px-3 py-2 text-stone-700" "Forth") + (td :class "px-3 py-2 text-amber-600" "Sort of") + (td :class "px-3 py-2 text-green-700" "Yes") + (td :class "px-3 py-2 text-green-700" "Yes") + (td :class "px-3 py-2 text-red-600" "No") + (td :class "px-3 py-2 text-red-600" "No")) + (tr :class "border-b border-stone-200 bg-violet-50" + (td :class "px-3 py-2 font-semibold text-violet-800" "S-expressions") + (td :class "px-3 py-2 text-green-700 font-semibold" "Yes") + (td :class "px-3 py-2 text-green-700 font-semibold" "Yes") + (td :class "px-3 py-2 text-green-700 font-semibold" "Yes") + (td :class "px-3 py-2 text-green-700 font-semibold" "Yes") + (td :class "px-3 py-2 text-green-700 font-semibold" "Yes"))))) + (p :class "text-stone-600" + "No candidate achieves all five properties. The closest — Rebol — fails on AI trainability, which is not a theoretical concern but a practical one: a representation that AI cannot generate reliably is a representation that cannot participate in the coming decade of software development.")) + + (~doc-section :title "Why not invent something new?" :id "invent" + (p :class "text-stone-600" + "The objection might be: fine, existing alternatives fall short, but why not design a new representation that has all these properties without the parentheses?") + (p :class "text-stone-600" + "Try it. You need a tree structure (for markup and composition). You need a uniform representation (for homoiconicity). You need a delimiter that is unambiguous (for one-pass parsing and structural validation). You need minimum syntactic overhead (for token efficiency).") + (p :class "text-stone-600" + "A tree needs a way to mark where a node begins and ends. The minimal delimiter pair is two characters — one for open, one for close. S-expressions use " (code "(") " and " (code ")") ". You could use " (code "[") " and " (code "]") ", or " (code "{") " and " (code "}") ", or " (code "BEGIN") " and " (code "END") ", or indentation. But " (code "[list]") " and " (code "{list}") " are just s-expressions with different brackets. " (code "BEGIN/END") " adds token overhead. Indentation adds whitespace sensitivity, which breaks wire-format reliability.") + (p :class "text-stone-600" + "You could try eliminating delimiters entirely and using a binary format. But binary formats are not human-readable, not human-writable, and not inspectable in a terminal — which means they cannot serve as a programming language. The developer experience of reading and writing code requires a text-based representation, and the minimal text-based tree delimiter is a matched pair of single characters.") + (p :class "text-stone-600" + "You could try significant whitespace — indentation-based nesting like Python or Haskell. This works for programming languages where the code is stored in files and processed by a single toolchain. It does not work for wire formats, where the representation must survive HTTP transfer, server-side generation, client-side parsing, minification, storage in databases, embedding in script tags, and concatenation with other expressions. Whitespace-sensitive formats are fragile in exactly the contexts where SX operates.") + (p :class "text-stone-600" + "Every path through the design space either arrives at parenthesized prefix notation — s-expressions — or introduces complexity that violates one of the requirements. This is not a failure of imagination. It is a consequence of the requirements being simultaneously demanding and precise. The solution space has one optimum, and McCarthy found it in 1958.")) + + (~doc-section :title "The parentheses objection" :id "parentheses" + (p :class "text-stone-600" + "The real objection to s-expressions is not technical. It is aesthetic. People do not like parentheses. They look unfamiliar. They feel old. They trigger memories of computer science lectures about recursive descent parsers.") + (p :class "text-stone-600" + "This is a human problem, not a representation problem. And it is a human problem with a known trajectory: every programmer who has used a Lisp for more than a few weeks stops seeing the parentheses. They see the tree. The delimiters become invisible, like the spaces between words in English. You do not see spaces. You see words. Lisp programmers do not see parentheses. They see structure.") + (p :class "text-stone-600" + "More to the point: in the world we are entering, most code will be generated by AI and rendered by machines. The human reads the " (em "output") " — the rendered page, the test results, the behaviour. The s-expressions are an intermediate representation that the human steers but does not need to manually type or visually parse character-by-character. The aesthetic objection dissolves when the representation is a conversation between the human's intent and the machine's generation, not something the human stares at in a text editor.") + (p :class "text-stone-600" + "The author of SX has never opened the codebase in an editor. Every file was created through " (a :href "https://claude.ai/" :class "text-violet-600 hover:underline" "Claude") " in a terminal. The parentheses are between the human and the machine, and neither one minds them.")) + + (~doc-section :title "The conclusion" :id "conclusion" + (p :class "text-stone-600" + "S-expressions are the minimal tree representation. They are the only widely-known homoiconic notation. They have trivial structural validation, maximum token efficiency, and native composability. They are well-represented in AI training data. Every alternative either fails on one of these criteria or converges toward s-expressions under a different name.") + (p :class "text-stone-600" + "This is not a claim that s-expressions are the best syntax for every programming language. They are not. Python's indentation-based syntax is better for imperative scripting. Haskell's layout rules are better for type-heavy functional programming. SQL is better for relational queries.") + (p :class "text-stone-600" + "The claim is narrower and stronger: for a system that must simultaneously serve as markup, programming language, wire format, data notation, spec language, and metaprogramming substrate — with homoiconicity, one-pass parsing, structural validation, token efficiency, and composability — there is no alternative to s-expressions. Not because alternatives have not been tried. Not because the design space has not been explored. But because the requirements, when stated precisely, admit exactly one family of solutions, and that family is the one McCarthy discovered sixty-seven years ago.") + (p :class "text-stone-600" + "The name for this, borrowed from a " (a :href "https://en.wikipedia.org/wiki/There_is_no_alternative" :class "text-violet-600 hover:underline" "different context entirely") ", is " (em "TINA") " — there is no alternative. Not as a political slogan, but as a mathematical observation. When you need a minimal, homoiconic, structurally-validatable, token-efficient, tree-native, AI-compatible representation for the web, you need s-expressions. Everything else is either less capable or isomorphic.")))) diff --git a/sx/sx/nav-data.sx b/sx/sx/nav-data.sx index 80589b0..b534a96 100644 --- a/sx/sx/nav-data.sx +++ b/sx/sx/nav-data.sx @@ -82,6 +82,8 @@ :summary "The web's HTML/CSS/JS split separates the framework's concerns, not your application's. Real separation is domain-specific.") (dict :label "SX and AI" :href "/essays/sx-and-ai" :summary "Why s-expressions are the most AI-friendly representation for web interfaces.") + (dict :label "There Is No Alternative" :href "/essays/no-alternative" + :summary "Every attempt to escape s-expressions leads back to s-expressions. This is not an accident.") (dict :label "sx sucks" :href "/essays/sx-sucks" :summary "An honest accounting of everything wrong with SX and why you probably shouldn't use it."))) diff --git a/sx/sxc/pages/docs.sx b/sx/sxc/pages/docs.sx index 56a9b9b..7f2f672 100644 --- a/sx/sxc/pages/docs.sx +++ b/sx/sxc/pages/docs.sx @@ -283,6 +283,7 @@ "server-architecture" (~essay-server-architecture) "separation-of-concerns" (~essay-separation-of-concerns) "sx-and-ai" (~essay-sx-and-ai) + "no-alternative" (~essay-no-alternative) :else (~essays-index-content))) ;; ---------------------------------------------------------------------------