Component names now reflect filesystem location using / as path separator and : as namespace separator for shared components: ~sx-header → ~layouts/header ~layout-app-body → ~shared:layout/app-body ~blog-admin-dashboard → ~admin/dashboard 209 files, 4,941 replacements across all services. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
94 lines
18 KiB
Plaintext
94 lines
18 KiB
Plaintext
(defcomp ~essays/sx-and-wittgenstein/essay-sx-and-wittgenstein ()
|
|
(~docs/page :title "SX and Wittgenstein"
|
|
(p :class "text-stone-500 text-sm italic mb-8"
|
|
"The limits of my language are the limits of my world.")
|
|
(~docs/section :title "I. Language games" :id "language-games"
|
|
(p :class "text-stone-600"
|
|
"In 1953, Ludwig " (a :href "https://en.wikipedia.org/wiki/Ludwig_Wittgenstein" :class "text-violet-600 hover:underline" "Wittgenstein") " published " (a :href "https://en.wikipedia.org/wiki/Philosophical_Investigations" :class "text-violet-600 hover:underline" "Philosophical Investigations") " — a book that dismantled the theory of language he had built in his own earlier work. The " (a :href "https://en.wikipedia.org/wiki/Tractatus_Logico-Philosophicus" :class "text-violet-600 hover:underline" "Tractatus") " had argued that language pictures the world: propositions mirror facts, and the structure of a sentence corresponds to the structure of reality. The Investigations abandoned this. Language does not picture anything. Language is " (em "used") ".")
|
|
(p :class "text-stone-600"
|
|
"Wittgenstein replaced the picture theory with " (a :href "https://en.wikipedia.org/wiki/Language_game_(philosophy)" :class "text-violet-600 hover:underline" "language games") " — activities in which words get their meaning from how they are employed, not from what they refer to. \"Slab!\" on a building site means \"bring me a slab.\" The same word in a dictionary means nothing until it enters a game. Meaning is use.")
|
|
(p :class "text-stone-600"
|
|
"Web development is a proliferation of language games. HTML is one game — a markup game where tags denote structure. CSS is another — a declaration game where selectors denote style. JavaScript is a third — an imperative game where statements denote behaviour. JSX is a fourth game layered on top of the third, pretending to be the first. TypeScript is a fifth game that annotates the third. Each has its own grammar, its own rules, its own way of meaning.")
|
|
(p :class "text-stone-600"
|
|
"SX collapses these into a single game. " (code "(div :class \"p-4\" (h2 title))") " is simultaneously structure (a div containing an h2), style (the class attribute), and behaviour (the symbol " (code "title") " is evaluated). There is one syntax, one set of rules, one way of meaning. Not because the distinctions between structure, style, and behaviour have been erased — they haven't — but because they are all expressed in the same language game."))
|
|
(~docs/section :title "II. The limits of my language" :id "limits"
|
|
(p :class "text-stone-600"
|
|
"\"" (a :href "https://en.wikipedia.org/wiki/Tractatus_Logico-Philosophicus" :class "text-violet-600 hover:underline" "Die Grenzen meiner Sprache bedeuten die Grenzen meiner Welt") "\" — the limits of my language mean the limits of my world. This is proposition 5.6 of the Tractatus, and it is the most important sentence Wittgenstein ever wrote.")
|
|
(p :class "text-stone-600"
|
|
"If your language is HTML, your world is documents. You can link documents. You can nest elements inside documents. You cannot compose documents from smaller documents without a server-side include, an iframe, or JavaScript. The language does not have composition, so your world does not have composition.")
|
|
(p :class "text-stone-600"
|
|
"If your language is React, your world is components that re-render. You can compose components. You can pass props. You cannot inspect a component's structure at runtime without React DevTools. You cannot serialize a component tree to a format another framework can consume. You cannot send a component over HTTP and have it work on the other side without the same React runtime. The language has composition but not portability, so your world has composition but not portability.")
|
|
(p :class "text-stone-600"
|
|
"If your language is s-expressions, your world is " (em "expressions") ". An expression can represent a DOM node, a function call, a style declaration, a macro transformation, a component definition, a wire-format payload, or a specification of the evaluator itself. The language has no built-in limits on what can be expressed, because the syntax — the list — can represent anything. The limits of the language are only the limits of what you choose to evaluate.")
|
|
(~docs/code :lang "lisp" :code
|
|
";; The same syntax expresses everything:\n(div :class \"card\" (h2 \"Title\")) ;; structure\n(css :flex :gap-4 :p-2) ;; style\n(defcomp ~essays/sx-and-wittgenstein/card (&key title) (div title)) ;; abstraction\n(defmacro ~log (x) `(console.log ,x)) ;; metaprogramming\n(quote (div :class \"card\" (h2 \"Title\"))) ;; data about structure")
|
|
(p :class "text-stone-600"
|
|
"Wittgenstein's proposition cuts both ways. A language that limits you to documents limits your world to documents. A language that can express anything — because its syntax is the minimal recursive structure — limits your world to " (em "everything") "."))
|
|
(~docs/section :title "III. Whereof one cannot speak" :id "silence"
|
|
(p :class "text-stone-600"
|
|
"The Tractatus ends: \"" (a :href "https://en.wikipedia.org/wiki/Tractatus_Logico-Philosophicus#Proposition_7" :class "text-violet-600 hover:underline" "Whereof one cannot speak, thereof one must be silent") ".\" Proposition 7. The things that cannot be said in a language simply do not exist within that language's world.")
|
|
(p :class "text-stone-600"
|
|
"HTML cannot speak of composition. It is silent on components. You cannot define " (code "~essays/sx-and-wittgenstein/card") " in HTML. You can define " (code "<template>") " and " (code "<slot>") " in Web Components, but that requires JavaScript — you have left HTML's language game and entered another.")
|
|
(p :class "text-stone-600"
|
|
"CSS cannot speak of conditions. It is silent on logic. You cannot say \"if the user is logged in, use this colour.\" You can use " (code ":has()") " and " (code "@container") " queries, but these are conditions about " (em "the document") ", not conditions about " (em "the application") ". CSS can only speak of what CSS can see.")
|
|
(p :class "text-stone-600"
|
|
"JavaScript can speak of almost everything — but it speaks in statements, not expressions. The difference matters. A statement executes and is gone. An expression evaluates to a value. Values compose. Statements require sequencing. React discovered this when it moved from class components (imperative, statement-oriented) to hooks (closer to expressions, but not quite — hence the rules of hooks).")
|
|
(p :class "text-stone-600"
|
|
"S-expressions are pure expression. Every form evaluates to a value. There is nothing that cannot be spoken, because lists can nest arbitrarily and symbols can name anything. There is no proposition 7 for s-expressions — no enforced silence, no boundary where the language gives out. The programmer decides where to draw the line, not the syntax."))
|
|
(~docs/section :title "IV. Family resemblance" :id "family-resemblance"
|
|
(p :class "text-stone-600"
|
|
"Wittgenstein argued that concepts do not have sharp definitions. What is a \"" (a :href "https://en.wikipedia.org/wiki/Family_resemblance" :class "text-violet-600 hover:underline" "game") "\"? Chess, football, solitaire, ring-a-ring-o'-roses — they share no single essential feature. Instead, they form a network of overlapping similarities. A " (a :href "https://en.wikipedia.org/wiki/Family_resemblance" :class "text-violet-600 hover:underline" "family resemblance") ".")
|
|
(p :class "text-stone-600"
|
|
"Web frameworks have this property. What is a \"component\"? In React, it is a function that returns JSX. In Vue, it is an object with a template property. In Svelte, it is a " (code ".svelte") " file. In Web Components, it is a class that extends HTMLElement. In Angular, it is a TypeScript class with a decorator. These are not the same thing. They share a family resemblance — they all produce reusable UI — but their definitions are incompatible. A React component cannot be used in Vue. A Svelte component cannot be used in Angular. The family does not communicate.")
|
|
(p :class "text-stone-600"
|
|
"In SX, a component is a list whose first element is a symbol beginning with " (code "~") ". That is the complete definition. It is not a function, not a class, not a file, not a decorator. It is a " (em "naming convention on a data structure") ". Any system that can process lists can process SX components. Python evaluates them on the server. JavaScript evaluates them in the browser. A future Rust evaluator could evaluate them on an embedded device. The family resemblance sharpens into actual identity: a component is a component is a component, because the representation is the same everywhere.")
|
|
(~docs/code :lang "lisp" :code
|
|
";; This is a component in every SX evaluator:\n(defcomp ~essays/sx-and-wittgenstein/greeting (&key name)\n (div :class \"p-4\"\n (h2 (str \"Hello, \" name))))\n\n;; The same s-expression is:\n;; - parsed by the same parser\n;; - evaluated by the same eval rules\n;; - rendered by the same render spec\n;; - on every host, in every context"))
|
|
(~docs/section :title "V. Private language" :id "private-language"
|
|
(p :class "text-stone-600"
|
|
"The " (a :href "https://en.wikipedia.org/wiki/Private_language_argument" :class "text-violet-600 hover:underline" "private language argument") " is one of Wittgenstein's most provocative claims: there can be no language whose words refer to the speaker's private sensations and nothing else. Language requires public criteria — shared rules that others can check. A word that means something only to you is not a word at all.")
|
|
(p :class "text-stone-600"
|
|
"Most web frameworks are private languages. React's JSX is meaningful only to the React runtime. Vue's " (code ".vue") " single-file components are meaningful only to the Vue compiler. Svelte's " (code ".svelte") " files are meaningful only to the Svelte compiler. Each framework speaks a language that no one else can understand. They are private languages in Wittgenstein's sense — their terms have meaning only within their own closed world.")
|
|
(p :class "text-stone-600"
|
|
"S-expressions are radically public. The syntax is universal: open paren, atoms, close paren. Any Lisp, any s-expression processor, any JSON-to-sexp converter can read them. The SX evaluator adds meaning — " (code "defcomp") ", " (code "defmacro") ", " (code "if") ", " (code "let") " — but these meanings are specified in s-expressions themselves (" (code "eval.sx") "), readable by anyone. There is no private knowledge. There is no compilation step that transforms the public syntax into a private intermediate form. The source is the artefact.")
|
|
(p :class "text-stone-600"
|
|
"This is why SX can be self-hosting. A private language cannot define itself — it would need a second private language to define the first, and a third to define the second. A public language, one whose rules are expressible in its own terms, can close the loop. " (code "eval.sx") " defines SX in SX. The language defines itself publicly, in a form that any reader can inspect."))
|
|
(~docs/section :title "VI. Showing and saying" :id "showing-saying"
|
|
(p :class "text-stone-600"
|
|
"The Tractatus makes a crucial distinction between what can be " (em "said") " and what can only be " (em "shown") ". Logic, Wittgenstein argued, cannot be said — it can only be shown by the structure of propositions. You cannot step outside logic to make statements about logic; you can only exhibit logical structure by using it.")
|
|
(p :class "text-stone-600"
|
|
"Most languages " (em "say") " their semantics — in English-language specifications, in RFC documents, in MDN pages. The semantics are described " (em "about") " the language, in a different medium.")
|
|
(p :class "text-stone-600"
|
|
"SX " (em "shows") " its semantics. The specification is not a description of the language — it is the language operating on itself. " (code "eval.sx") " does not say \"the evaluator dispatches on the type of expression.\" It " (em "is") " an evaluator that dispatches on the type of expression. " (code "parser.sx") " does not say \"strings are delimited by double quotes.\" It " (em "is") " a parser that recognises double-quoted strings.")
|
|
(p :class "text-stone-600"
|
|
"This is exactly the distinction Wittgenstein drew. What can be said (described, documented, specified in English) is limited. What can be shown (exhibited, demonstrated, enacted) goes further. SX's self-hosting spec shows its semantics by " (em "being") " them — the strongest form of specification possible. The spec cannot be wrong, because the spec runs."))
|
|
(~docs/section :title "VII. The beetle in the box" :id "beetle"
|
|
(p :class "text-stone-600"
|
|
"Wittgenstein's " (a :href "https://en.wikipedia.org/wiki/Private_language_argument#The_beetle_in_a_box" :class "text-violet-600 hover:underline" "beetle-in-a-box") " thought experiment: suppose everyone has a box, and everyone calls what's inside their box a \"beetle.\" No one can look in anyone else's box. The word \"beetle\" refers to whatever is in the box — but since no one can check, the contents might be different for each person, or the box might even be empty. The word gets its meaning from the " (em "game") " it plays in public, not from the private contents of the box.")
|
|
(p :class "text-stone-600"
|
|
"A web component is a beetle in a box. You call it " (code "<my-button>") " but what's inside — Shadow DOM, event listeners, internal state, style encapsulation — is private. Two components with the same tag name might do completely different things. Two frameworks with the same concept of \"component\" might mean completely different things by it. The word \"component\" functions in the language game of developer conversation, but the actual contents are private to each implementation.")
|
|
(p :class "text-stone-600"
|
|
"In SX, you can open the box. Components are data. " (code "(defcomp ~essays/sx-and-wittgenstein/card (&key title) (div title))") " — the entire definition is visible, inspectable, serializable. There is no shadow DOM, no hidden state machine, no encapsulated runtime. The component's body is an s-expression. You can quote it, transform it, analyse it, send it over HTTP, evaluate it in a different context. The beetle is on the table."))
|
|
(~docs/section :title "VIII. The fly-bottle" :id "fly-bottle"
|
|
(p :class "text-stone-600"
|
|
"\"What is your aim in philosophy?\" Wittgenstein was asked. \"" (a :href "https://en.wikipedia.org/wiki/Philosophical_Investigations#Meaning_and_definition" :class "text-violet-600 hover:underline" "To show the fly the way out of the fly-bottle") ".\"")
|
|
(p :class "text-stone-600"
|
|
"The fly-bottle is the trap of false problems — questions that seem deep but are actually artefacts of confused language. \"What is the virtual DOM?\" is a fly-bottle question. The virtual DOM exists because React needed a way to reconcile its component model with the browser's DOM. If your component model " (em "is") " the DOM — if components evaluate directly to DOM nodes, as they do in SX — the question dissolves. There is no virtual DOM because there is no gap between the component and what it produces.")
|
|
(p :class "text-stone-600"
|
|
"\"How do we solve CSS scoping?\" Fly-bottle. CSS scoping is a problem because CSS is a global language applied to a local context. If styles are expressions evaluated in the same scope as the component that uses them — as in SX's CSSX — there is nothing to scope. The problem was created by the language separation, not by the nature of styling.")
|
|
(p :class "text-stone-600"
|
|
"\"How do we share state between server and client?\" Fly-bottle. This is hard when the server speaks one language (Python templates) and the client speaks another (JavaScript). When both speak s-expressions, and the wire format IS the source syntax, state transfer is serialisation — which is identity for s-expressions. " (code "aser") " serialises an SX expression as an SX expression. The server and client share state by sharing code.")
|
|
(p :class "text-stone-600"
|
|
"SX does not solve these problems. It dissolves them — by removing the language confusion that created them. Wittgenstein's method, applied to web development: the problems were never real. They were artefacts of speaking in the wrong language."))
|
|
(~docs/section :title "IX. Back to rough ground" :id "rough-ground"
|
|
(p :class "text-stone-600"
|
|
"\"We have got on to slippery ice where there is no friction and so in a certain sense the conditions are ideal, but also, just because of that, we are unable to walk. We want to walk: so we need " (a :href "https://en.wikipedia.org/wiki/Philosophical_Investigations" :class "text-violet-600 hover:underline" "friction") ". Back to the rough ground!\"")
|
|
(p :class "text-stone-600"
|
|
"The slippery ice is the idealised abstraction — the framework that promises a perfect developer experience, the type system that promises to catch all bugs, the architecture diagram that promises clean separation. These are frictionless surfaces. They look beautiful. You cannot walk on them.")
|
|
(p :class "text-stone-600"
|
|
"SX is rough ground. S-expressions are not pretty. Parentheses are friction. There is no syntax highlighting tuned for this. There is no IDE with SX autocomplete. There is no build system to smooth over rough edges because there is no build system. You write s-expressions. You evaluate them. You see the result. The feedback loop is immediate and unmediated.")
|
|
(p :class "text-stone-600"
|
|
"This is a feature. Friction is how you know you are touching the ground. Friction is where the work happens. The perfectly frictionless framework lets you get started in five minutes and spend six months debugging its abstractions. SX asks you to understand s-expressions — a syntax that has not changed since 1958 — and then gives you the entire web as your world.")
|
|
(p :class "text-stone-600"
|
|
"Wittgenstein wanted philosophy to stop theorising and start looking at how language actually works. SX wants web development to stop abstracting and start looking at what expressions actually are. Lists. Symbols. Evaluation. Composition. Everything else is a fly-bottle."))))
|