A type variable moves unless it says otherwise, and the clause is a map
This commit is contained in:
parent
b6bb1e502b
commit
4072dd2089
24
NEXT.md
24
NEXT.md
@ -27,6 +27,13 @@ clause tells the abstract pass what it may assume, so the body checks at the def
|
|||||||
|
|
||||||
- A type variable binds as `$t` in a signature and is used bare. Already decided; see the sigil entry.
|
- A type variable binds as `$t` in a signature and is used bare. Already decided; see the sigil entry.
|
||||||
- A generic body is checked **abstractly**, once, with nothing substituted.
|
- A generic body is checked **abstractly**, once, with nothing substituted.
|
||||||
|
- The clause is written as a **Clojure-style map at the head of the body**, `{:where (ordered? $t)}`, chosen by
|
||||||
|
the author over a bare keyword. It disambiguates because a bare `{}` in expression position is already refused
|
||||||
|
(`parse.ml:110`), so a `{}` there can be nothing else, and Clojure's `{:pre [...] :post [...]}` is the
|
||||||
|
precedent. It leaves room for further keys without new syntax. **One catch to settle first:** `{K V}` is
|
||||||
|
currently a legal return type, so `(defn f [...] {string i32} {:where ...} body)` puts two braces in a row
|
||||||
|
meaning different things. That resolves itself if `{K V}` goes in favour of `(Map K V)`, which is a separate
|
||||||
|
open question in this file.
|
||||||
- A `where` clause over **compile-time type predicates** admits the operators the body needs. Four are wanted —
|
- A `where` clause over **compile-time type predicates** admits the operators the body needs. Four are wanted —
|
||||||
`ordered?`, `equal?`, `hashable?`, `numeric?` — against Odin's forty-one. The prelude's nine non-collapsing
|
`ordered?`, `equal?`, `hashable?`, `numeric?` — against Odin's forty-one. The prelude's nine non-collapsing
|
||||||
functions need only the first two.
|
functions need only the first two.
|
||||||
@ -60,6 +67,23 @@ The prelude keeps a per-type layer for the numeric ones. That is the honest numb
|
|||||||
|
|
||||||
### Still open, and not blocking
|
### Still open, and not blocking
|
||||||
|
|
||||||
|
0. ~~**Ownership.**~~ **Decided by the author.** A type variable is **move-only by default**, and a
|
||||||
|
`copyable?` predicate in the `where` clause is what tells the checker otherwise. Conservative in the safe
|
||||||
|
direction: move rules are the stricter ones, so assuming them can only refuse programs, never admit a bad one.
|
||||||
|
It costs nothing at run time — the predicate changes what the checker permits and emits no code.
|
||||||
|
|
||||||
|
**Odin is no help here and this is worth knowing before someone goes looking.** Odin has no move semantics and
|
||||||
|
no ownership tracking at all: `Raw_Dynamic_Array` (`base/runtime/core.odin:523`) is a plain struct of pointer,
|
||||||
|
length, capacity and allocator, freely copyable, and an alias is the programmer's problem to notice. Nothing in
|
||||||
|
its compiler tracks it, so `$T` never has to answer this. **The prior art is Rust**, where a parameter defaults
|
||||||
|
to move and `T: Copy` is the opt-in. One difference to keep: Rust's `Copy` is a trait a user may implement,
|
||||||
|
while `copyable?` is a predicate the compiler answers, because it already knows which types own heap storage.
|
||||||
|
Same ergonomics, none of the trait machinery, consistent with the `where` decision above.
|
||||||
|
|
||||||
|
What this means in the body: a generic may not use a parameter twice unless it declares `copyable?`.
|
||||||
|
`(defn twice [x $t] $t (+ x x))` is refused without it — correct at `i32`, wrong at `(Vec i32)`, and the
|
||||||
|
checker cannot tell which until it substitutes.
|
||||||
|
|
||||||
1. **The runaway refusal.** `(defn grow [x $t] () (grow [x x]))` asks for a copy at `[2 t]`, then `[2 [2 t]]`,
|
1. **The runaway refusal.** `(defn grow [x $t] () (grow [x x]))` asks for a copy at `[2 t]`, then `[2 [2 t]]`,
|
||||||
forever. Before the spike's cap it did not fail, it **hung** — and `Session.eval` runs the same code, so what
|
forever. Before the spike's cap it did not fail, it **hung** — and `Session.eval` runs the same code, so what
|
||||||
hangs is `C-c C-c` with the daemon wedged behind it and nothing to show. The cap is a depth counter refusing
|
hangs is `C-c C-c` with the daemon wedged behind it and nothing to show. The cap is a depth counter refusing
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user