diff --git a/DISCUSS.md b/DISCUSS.md index aba96b2..6bdbbf1 100644 --- a/DISCUSS.md +++ b/DISCUSS.md @@ -106,14 +106,40 @@ conditional re-initialisation. Neither is worth building alone; together they ar ## 4. Structural typing, row polymorphism, anonymous structs -Worth noting before this is scoped: `types.ml` already does **structural equality** on resolved types, and a Flan struct -is exactly its C layout with no header or tag word. So the representation is already structural; what is nominal is the -*checking*, not the data. +The goal as stated: make this feel Clojure-like while still being typed. -The questions this opens: whether a function can take "any struct with an `x` and a `y`"; whether anonymous structs get a -spelling; how this interacts with the FFI, where the shim generates a C typedef per named struct; and how it interacts -with the planned managed classes, which are explicitly the *opposite* direction — identity and metadata rather than plain -layout. +**The representation is already structural.** `types.ml` does structural equality on resolved types, and a Flan struct is +exactly its C layout with no header and no tag word. What is nominal is the *checking*, not the data — so this is a +front-end question, not a data-model change. + +**What it buys, in rough order of value here:** + +1. **Your destructuring already looks like this, and that is the strongest argument.** Clojure's `{:keys [x y]}` works in + binding position today. Structural typing makes the same notation work in *parameter* position — the pattern you write + to pull fields out becomes the signature saying what you accept. One notation, two places, no separate declaration. + That is precisely the "Clojure-like but typed" feel being asked for, and it needs no new syntax, only a new meaning + for syntax that exists. +2. **Functions over "anything with these fields".** Magnitude over anything with an `x` and a `y` — a position, a + velocity, an enemy, a bullet. In a game this recurs constantly because everything has a position. +3. **Ad-hoc returns.** Returning a hit flag and a point without declaring a type for the pair. The Clojure habit of + returning a map, typed and free. +4. **The FFI stops needing conversions.** raylib's `Vector2` and a locally-defined vector are byte-identical and are + today two nominal types. Structurally they are one. + +**Two costs, and the second is the real work:** + +- **It cuts against the project's standing instinct.** This language has repeatedly chosen explicit over convenient: no + implicit numeric conversions, a bare integer cannot become an enum, `:spcae` must fail at the call site. Structural + typing says things are compatible when they happen to line up, which is the opposite reflex. Worth deciding + deliberately rather than drifting into — and worth asking whether structural compatibility should be *written* at the + site that relies on it, the way `(GamepadAxis n)` made an enum conversion explicit without making it implicit. +- **Field order is layout, and that is the hard part.** `{x f32, y f32}` and `{y f32, x f32}` have the same fields and + different memory. So either field order becomes significant — surprising, since nothing else about a structural type + should be — or matching a differently-ordered type requires a copy, which breaks the zero-cost property the whole data + model rests on. This is the question to settle first; the type checking is comparatively easy. + +Also unresolved: the shim generates a C typedef per *named* struct, and an anonymous struct has no name to generate one +from. Either anonymous structs cannot cross the FFI, or the generator learns to name them. ## 5. A performant JS transpiler