Why a typed let binding is a grammar question, written down not guessed
Looked at annotating a let binding and stopped at the surface syntax, which is the whole of the problem. Everything underneath is already built: bindings carry a type, load renames through it, and the checker consumes it as the want for the value. What is missing is a way to write it that a parser with no types can read -- let is a flat list of pairs, so it cannot disambiguate by argument count the way defvar and defconst do, and [4 rl/Vector2] is a perfectly good array literal. So NEXT.md gets the three candidate surfaces and a recommendation rather than a commit picking one: give zeroed its type as an argument. It is one branch in the checker, no new syntax, and it answers the case that actually hurt -- a fixed array with nothing to infer from -- without contradicting plan.org's "annotate function signatures, infer locals". The two items beside it in the same ranked list are marked fixed.
This commit is contained in:
parent
387ceb7a2e
commit
9864f41aca
30
NEXT.md
30
NEXT.md
@ -225,10 +225,32 @@ Ranked by how often they were hit, top two first because they are walls rather t
|
||||
3. **`break` is not implemented.** Declined deliberately rather than built — see below.
|
||||
4. **A `let` binding takes no type annotation**, so a fixed array is either a top-level `defvar` or a literal with
|
||||
every element spelled out. `(let [pts [4 rl/Vector2]] …)` parses as a two-element array literal and fails with
|
||||
*unknown name rl/Vector2*. Cost: 32 hand-written `Vector2`s in one example.
|
||||
5. **Arithmetic is strictly binary** — *+ takes 2 arguments, given 5*.
|
||||
6. **No `sin`/`cos`/`abs` for floats.** The prelude has `sqrt-f32` and nothing else transcendental. Two `declare`
|
||||
lines over libm, and `(max x (- 0.0 x))`; the examples each declare their own, which is a copy per file.
|
||||
*unknown name rl/Vector2*. Cost: 32 hand-written `Vector2`s in one example. **Looked at and stopped — it is a
|
||||
grammar question, not a missing feature.** Everything under the surface is already there: `Ast.binding` carries a
|
||||
`bty`, `load.ml` renames through it, and `check.ml:723` consumes it as the `want` for the value. Only the way it
|
||||
is written is open, and the parser says so where it refuses (`parse.ml:366`): `let` is a flat list of pairs, so it
|
||||
cannot disambiguate by *count* the way `defvar` and `defconst` do — those read `[n t v]` as three arguments to a
|
||||
form, and there is no such boundary between one pair and the next. Three surfaces, in the order they are worth
|
||||
considering:
|
||||
- **`(zeroed [4 rl/Vector2])` — `zeroed` takes its type as an argument.** Recommended. It is one extra branch in
|
||||
the arity-0 `zeroed` case in `check.ml`, no parser change, no ambiguity, and it answers the actual complaint,
|
||||
which is not "locals cannot be annotated" but "there is nothing here to infer *from*". It also reads as what it
|
||||
does: the value is a zeroed thing of that type, not a name that has been told what it is.
|
||||
- **A marker between the name and the type**, `(let [pts :- [4 rl/Vector2] …] …)` or similar. Unambiguous, and it
|
||||
buys a general annotation rather than one form's escape hatch. The cost is a new piece of syntax in the binding
|
||||
vector, which is the one place this language has kept looking exactly like Clojure's.
|
||||
- **Bare `(let [pts [4 rl/Vector2] …])`.** The obvious spelling and the one that cannot work: `[4 rl/Vector2]` is
|
||||
a well-formed two-element array literal, and telling the two apart needs types in the parser, which there are
|
||||
none of by design.
|
||||
Note that plan.org's rule is "annotate function signatures, infer locals", so the general annotation is a
|
||||
deliberate absence and not an oversight — which is the other reason the `zeroed` route is the smaller answer.
|
||||
5. ~~**Arithmetic is strictly binary** — *+ takes 2 arguments, given 5*.~~ **Fixed.** `+ - * /`, `min`/`max` and
|
||||
`bit-and`/`bit-or`/`bit-xor` fold left over two operands or more. `%` and the shifts stay at two, and one operand
|
||||
is refused with the form to write instead — there is no unary minus and no reciprocal.
|
||||
6. ~~**No `sin`/`cos`/`abs` for floats.**~~ **Fixed.** `sin-f32` and `cos-f32` are `declare`s in the prelude now,
|
||||
with the caveat written beside them: IEEE-754 makes `sqrt` correctly rounded and requires nothing of the kind for
|
||||
`sinf`, so these are the one place in the prelude where native and wasm32 may disagree bit for bit. Float `abs` is
|
||||
not wrapped, for the reason integer `abs` is not — it is `(max x (- 0.0 x))` over two builtins.
|
||||
|
||||
**A `string` cannot be returned from C at all**, which is what makes `GetGamepadName` unbindable: *a string only
|
||||
crosses as a parameter — a C function that returns one returns something Flan has no owner for*. Same rule refuses
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user