The return type stops being a guess, and recur beats silent TCO
This commit is contained in:
parent
ef9aebd807
commit
1393d817b0
42
NEXT.md
42
NEXT.md
@ -678,6 +678,48 @@ destination is always honest about what it is.
|
|||||||
|
|
||||||
Drop Clojure's `:eduction` branch — that is the pass-around case, and the one part that would need runtime machinery.
|
Drop Clojure's `:eduction` branch — that is the pass-around case, and the one part that would need runtime machinery.
|
||||||
|
|
||||||
|
## Queued: the return type is mandatory, and `loop`/`recur`
|
||||||
|
|
||||||
|
**1. A `defn` must always state its return type, and unit is written `()`.**
|
||||||
|
|
||||||
|
Today the slot is optional and the parser decides return-type-from-body by looking the symbol up in a set of known type
|
||||||
|
names. It is *sound* only because there is one top-level namespace, so a name cannot be both a type and a value — and
|
||||||
|
it is brittle because the set has to be complete. **It has been wrong twice in one day**, both from the same root: the
|
||||||
|
prelude's type names were not in the set, so `Form` in return position read as an unknown value; and fixing that
|
||||||
|
exposed a second arm reading the same set, which had been parsing `(defn f [] (Rune {.code 65}) (bar))` as a function
|
||||||
|
*returning* a `Rune` with a one-form body — **silently, in every file in the language**. The diagnostic is poor too:
|
||||||
|
`(defn f [] f65 0.0)` says *unknown name* rather than *did you mean f64*.
|
||||||
|
|
||||||
|
A silent misparse is the worst failure class available, and macros now generate definitions, which widens it.
|
||||||
|
**Mandatory removes the guess entirely** — the slot after the parameters is unconditionally a type, the parser stops
|
||||||
|
consulting a table, and the typo case gets a real message. The cost is `(defn main [] () ...)` on void functions,
|
||||||
|
against `plan.org`'s deliberate short form. Take the cost.
|
||||||
|
|
||||||
|
**Unit is `()`**, ML's spelling: it is the honest name, and it cannot collide because an empty call is not a valid
|
||||||
|
expression anyway.
|
||||||
|
|
||||||
|
Alternatives considered and rejected: a separate signature form, Typed Racket's `(: foo (-> i32 i32))` — removes the
|
||||||
|
ambiguity equally but splits the signature from the body and gives two things to keep in sync. Return type *before*
|
||||||
|
the parameters — does not work, because a fixed-array type is spelled with brackets, so `(defn foo [4 i32] ...)` could
|
||||||
|
be either. Inferring the return type — it is local and therefore cheap, but it only helps if annotating is
|
||||||
|
*forbidden*, and recursion forces an annotation back anyway, which makes it optional, which is where we started.
|
||||||
|
|
||||||
|
Note the function *type* spelling is already decided and needs no arrows: `(Fn [i32 i32] i32)`, mirroring `defn`.
|
||||||
|
Arrows would imply currying, which this language does not do.
|
||||||
|
|
||||||
|
**2. `loop` and `recur`.** Both are already refused by name in `parse.ml`. There is **no TCO** — nothing emits tail
|
||||||
|
calls, and `plan.org` mentions them only as something the current backend choice *could* control (LLVM's `musttail` is
|
||||||
|
there if wanted).
|
||||||
|
|
||||||
|
`recur` is the better answer than silent TCO, and not only because it is cheaper. **It is checked**: the compiler
|
||||||
|
verifies the call is in tail position and turns it into a jump, so breaking tail position is a compile error rather
|
||||||
|
than a stack overflow at run time. Clojure adopted it because the JVM lacks TCO and it turned out to be the better
|
||||||
|
design.
|
||||||
|
|
||||||
|
Cheap here — a jump to the top of a `loop`, which is the machinery `while` and the new labelled `break`/`continue`
|
||||||
|
already have. What it does **not** give is mutual recursion between two functions; that needs real tail calls, and is
|
||||||
|
a separate question if it is ever wanted.
|
||||||
|
|
||||||
## The next batch, in order
|
## The next batch, in order
|
||||||
|
|
||||||
Agreed at the end of 2026-09-12. Ordered by priority, not by size. Items 1-3 and 5-6 want the compiler core and should
|
Agreed at the end of 2026-09-12. Ordered by priority, not by size. Items 1-3 and 5-6 want the compiler core and should
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user