Nine questions asked and not answered, with what is already known about each
This commit is contained in:
parent
8d56bd0ced
commit
ce9ea2f268
134
DISCUSS.md
Normal file
134
DISCUSS.md
Normal file
@ -0,0 +1,134 @@
|
||||
# Let's discuss
|
||||
|
||||
Open questions, raised and deliberately not answered yet. Nothing here is a decision or a task. Each entry is the
|
||||
question as asked, plus what is already known in this repo that bears on it — so the investigation starts from what
|
||||
exists rather than from scratch.
|
||||
|
||||
Settled decisions live in `NEXT.md`. Reasons for what already exists live in `BUILT.md`.
|
||||
|
||||
---
|
||||
|
||||
## 1. Inspecting globals, separately, in the break buffer
|
||||
|
||||
Locals are readable now; globals are not shown anywhere. They are arguably the more useful half in this language today,
|
||||
because a Flan game keeps most of its state in top-level `defvar`s — `sand.flan` holds its entire grid that way.
|
||||
|
||||
What already exists: the daemon's `describe` op returns the program's globals by name, and `Session.render` walks a
|
||||
concrete type to a printed value. The `eval-expr` path already renders a global by name inside the running program. So
|
||||
the machinery is there and the question is mostly one of presentation — its own section in the break buffer, folded like
|
||||
the stack, or a separate buffer.
|
||||
|
||||
The real question is **which** globals. A program has hundreds and a listing of all of them is the backtrace problem
|
||||
again: the thing you want is buried by the thing you don't. Worth considering: only globals the stopped function
|
||||
mentions, which the compiler knows; or a filter; or a pinned watch list.
|
||||
|
||||
## 2. The break buffer should appear by itself
|
||||
|
||||
Today a condition stops the program and the buffer only opens when `C-c C-b` is typed. It should `pop-to-buffer` the
|
||||
moment the program stops.
|
||||
|
||||
What already exists: `flan-dev--absorb` inspects every reply for `:stopped`, and there is a poll for the case where no
|
||||
reply is pending — the client already *knows* the moment it stops, and already updates the mode line from it. So this is
|
||||
a hook at a point that exists, not new plumbing.
|
||||
|
||||
To decide: whether it steals focus or only displays; whether it should also fire for a `(pause)`, which is a deliberate
|
||||
stop and arguably always wants the window; and what happens when it stops while point is mid-edit in another buffer.
|
||||
|
||||
## 3. What is `i` supposed to inspect in the stack section?
|
||||
|
||||
Raised as a question about intent, and it deserves one, because the current answer may be wrong rather than merely
|
||||
undocumented.
|
||||
|
||||
`i` on a local passes that local's **name** to the inspector, which evaluates it as an expression inside the running
|
||||
program. That is correct for the innermost frame, where the name is in scope. **It is not obviously correct for any
|
||||
other frame** — evaluating `y` while looking at frame 3 evaluates `y` wherever the evaluator stands, not in frame 3.
|
||||
It may resolve to a global, to a different binding, or fail.
|
||||
|
||||
The locals *listing* does not have this problem: it renders from the frame's own slot addresses. So the display is
|
||||
frame-accurate and the inspector may not be. Options: root the inspector at the slot's address the way the listing is
|
||||
rooted; refuse `i` outside the innermost frame; or make the inspector frame-aware. Worth settling before anyone relies
|
||||
on it.
|
||||
|
||||
## 4. Annotating the IR and the disassembly with the source
|
||||
|
||||
The compiler should interleave the originating Flan expression into both the emitted LLVM IR and the disassembly
|
||||
listing.
|
||||
|
||||
What already exists: `emit.ml` writes `.ll` as text, so a comment costs nothing and cannot break anything. Every typed IR
|
||||
node carries a `Loc.t`. DWARF is emitted under `--debug`, with a line table naming the `.flan` file, and the disassembly
|
||||
listing already rebases addresses and annotates cells, calls and branch targets. So both halves have the information
|
||||
already; what is missing is the interleaving.
|
||||
|
||||
The IR half is nearly free. The disassembly half is more interesting and more valuable — it is what would let you see
|
||||
what one line of Flan actually costs, including the indirection cell a dev build puts on every cross-function call.
|
||||
|
||||
## 5. `def`, `defvar`, `defconst`
|
||||
|
||||
There is no `def`. There is `defvar` (mutable global) and `defconst` (compile-time constant, folded).
|
||||
|
||||
The proposal: a `def` that is mutable, where `defvar` only rewrites the value if the variable is *new* — Common Lisp's
|
||||
actual `defvar` semantics, where re-evaluating a `defvar` deliberately does not clobber a value you have been building up
|
||||
at runtime. That distinction matters much more here than in most languages, because `C-c C-k` on a whole buffer
|
||||
re-evaluates every top-level form against a *running* program, and today that resets state you may have spent a session
|
||||
accumulating.
|
||||
|
||||
`defconst` under redefinition is the unclear one, as noted — it is folded into its use sites, so changing one is closer to
|
||||
a recompile than to an assignment.
|
||||
|
||||
## 6. 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 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.
|
||||
|
||||
## 7. A performant JS transpiler
|
||||
|
||||
Asked as a feasibility question. Bear in mind the web target already exists and ships real machine code via wasm, so this
|
||||
is not the only route to a browser and the case for it needs stating: smaller artifacts, no wasm toolchain, debuggability
|
||||
in browser devtools, or something else.
|
||||
|
||||
The hard parts are the ones the wasm target got for free from clang: the memory model (Flan is pointers and explicit
|
||||
layout; JS is not), the FFI, and the fact that the whole raylib layer is C. A JS backend that cannot run raylib is a
|
||||
different product from the one that can.
|
||||
|
||||
## 8. C interop as seamless as Zig's
|
||||
|
||||
Today: `declare-c` names one C function per line, and the compiler generates the wrapper, the typedefs and the flattened
|
||||
declaration. 175 of them for raylib. **No header is ever read, deliberately** — which means nothing can check that a
|
||||
declaration matches the real signature, and that is written down as trusted rather than guaranteed.
|
||||
|
||||
The proposal is to read the header, prefix a namespace, and get `rl/InitWindow` for free — plus possibly automatic
|
||||
kebab-casing to `rl/init-window`.
|
||||
|
||||
This is a large change in kind, not just in size: it means a C parser or a libclang dependency in the build, and it
|
||||
trades an explicit, checkable list for an implicit surface. Worth weighing against what `declare-c` already buys, which
|
||||
is that a binding is one line and the wrapper is generated. The auto-kebab-case question is separable and much smaller —
|
||||
and note the FFI currently keeps C's own spelling on purpose, so the mapping would need to be reversible.
|
||||
|
||||
## 9. Where `defclass` stands
|
||||
|
||||
Specified in `plan.org` and **deliberately not started** — its own last line says nothing happens until ordinary
|
||||
`struct`, `Handle` and reload semantics work. Those have largely landed since that was written, so the gate may be closer
|
||||
than the document assumes.
|
||||
|
||||
What it is meant to add: identity, runtime shape metadata, an implementation-defined representation, generic-function
|
||||
dispatch, and live schema change with an explicit migration at a frame boundary — which is the answer to the one thing
|
||||
redefinition still cannot do, changing a struct's layout while instances exist.
|
||||
|
||||
Three findings from an earlier review, recorded in `NEXT.md` and not yet in `plan.org`:
|
||||
|
||||
- **A generic function is a cell.** Adding a method from a later module is the same problem indirection cells already
|
||||
solve, so the expensive half is built and tested.
|
||||
- **The pool is not one storage option among three.** `migrate-instances` has to enumerate live instances, which a pool
|
||||
behind a generational `Handle` gives by construction and the other two options do not.
|
||||
- **Every layout version must stay resolvable** for as long as any instance holds it — the same rule as "nothing is ever
|
||||
`dlclose`d".
|
||||
|
||||
Also open and related: whether a *condition* can be a class, which decides whether handler matching has one path or two.
|
||||
`NEXT.md` records the argument; decision 4 there took the cheaper parent-link route for now and explicitly left real
|
||||
inheritance possible later.
|
||||
4
NEXT.md
4
NEXT.md
@ -4,6 +4,10 @@
|
||||
|
||||
**Branch `dev-loop`, 199 commits, working tree clean, `dune test` green.**
|
||||
|
||||
**[`DISCUSS.md`](DISCUSS.md) is what has been *asked* and not answered** — open questions with the repo context that
|
||||
bears on each, so an investigation starts from what exists. Nothing in it is a decision or a task; when one becomes
|
||||
either, it moves here.
|
||||
|
||||
**`NEXT.md` is what is left. [`BUILT.md`](BUILT.md) is why the existing parts are the shape they are** — the reload
|
||||
primitive, cells, the agent, the session, the daemon, the Emacs client, conditions, the FFI shim, the layout, and the
|
||||
order it all got built in. This file was half build log until it was split; do not let it become one again. When a
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user