diff --git a/TODO.org b/TODO.org index 596144f2..ab7f45ce 100644 --- a/TODO.org +++ b/TODO.org @@ -916,11 +916,6 @@ Refused by name, narrower than the spec's key set; a struct holding the array works. It needs the per-element walk a struct key gets, driven by a loop rather than a field list. -** TODO map-keys and map-values cannot be prelude functions -Iteration is built; the remaining refusal is generics. A =defn= has to name its -types and =(defn map-keys [m (Map K V)] (Vec K))= has no =K=. The loop is three -lines at the call site, where =K= is known. - ** DONE (vec-new [u8]) is refused CLOSED: [2026-09-25] The type positions of =vec-new= and =map-new= take a type expression: brackets, or diff --git a/lib/check.ml b/lib/check.ml index ee0c4daf..d928e7bb 100644 --- a/lib/check.ml +++ b/lib/check.ml @@ -9434,9 +9434,7 @@ and named_call ?(qualified = false) ctx ~want loc name args = (while (map-next m (addr cur) (addr k) (addr v)) ...)) - It is *not* a generic (map-keys m): a Vec of them needs a signature naming - K, and a prelude defn cannot be written at every K. That one is generics, - not iteration, and it stays refused for that reason. + The prelude's map-keys and map-values are this loop over a generic key. No hash and no equality pair go with it — walking asks nothing about a key — so this is the one map entry point whose signature carries neither, diff --git a/lib/prelude.ml b/lib/prelude.ml index 904da3c9..36cc40de 100644 --- a/lib/prelude.ml +++ b/lib/prelude.ml @@ -564,6 +564,30 @@ let source = {flan| (push v (at s i)))) v)) +;; A map's keys, and its values, as a new Vec the caller owns. In block order, +;; which is the hash's and not the insertion's — sort what comes back if the +;; order matters. A string key is copied as the view it is, so the Vec reads +;; the map's own key bytes and is good for as long as they are. +(defn map-keys [m (Map $k $v)] (Vec $k) + {:where (hashable? $k)} + (let [out (vec-new k) + cur (i64 0) + key (the $k (zeroed)) + val (the $v (zeroed))] + (while (map-next m (addr cur) (addr key) (addr val)) + (push out key)) + out)) + +(defn map-values [m (Map $k $v)] (Vec $v) + {:where (hashable? $k)} + (let [out (vec-new v) + cur (i64 0) + key (the $k (zeroed)) + val (the $v (zeroed))] + (while (map-next m (addr cur) (addr key) (addr val)) + (push out val)) + out)) + ;; ── The sign questions, over every numeric type at once ─────────────── ;; ;; The family the whole of generics was asked for. Three questions about a @@ -1925,15 +1949,6 @@ let source = {flan| ;; that did not come with them, because it is one copy ;; per *ordered pair* of types rather than per type, ;; which is where a per-type family stops being honest. -;; map-keys, map-values Generics — and the reason changed, which is the -;; point of naming them separately. It used to be the -;; missing Map iterator; `map-next` is that iterator -;; and walking a map is expressible now. What a defn -;; still cannot say is (defn map-keys [m {K V}] (Vec K)): -;; a prelude function has to name its types, and there -;; is no K. The loop is three lines at the call site, -;; where K is known, and that is where it stays until -;; there are generics. ;; ;; Builder Not refused — declined. strings.Builder in Odin ;; wraps a [dynamic]u8; here the (Vec u8) *is* that and diff --git a/test/programs/map-keys.flan b/test/programs/map-keys.flan new file mode 100644 index 00000000..6bd61733 --- /dev/null +++ b/test/programs/map-keys.flan @@ -0,0 +1,34 @@ +;; map-keys and map-values: the prelude's two generic walks over a map. Block +;; order is the hash's, so what comes back is sorted before it is printed. + +(defn main [] i32 + (let [m (map-new i32 i64)] + (put m 30 (i64 300)) + (put m 10 (i64 100)) + (put m 20 (i64 200)) + (let [ks (map-keys m) + vs (map-values m)] + (sort (slice ks)) + (sort (slice vs)) + (dotimes [i (length ks)] (print (at ks i)) (print " ")) + (println "") + (dotimes [i (length vs)] (print (at vs i)) (print " ")) + (println "") + (free ks) + (free vs))) + ;; A string key, and a map that never allocated. + (let [names (map-new string i32) + none (map-new string i32)] + (put names "b" 2) + (put names "a" 1) + (let [ks (map-keys names) + nk (map-keys none) + total 0] + (dotimes [i (length ks)] (set total (+ total (or-else (get names (at ks i)) 0)))) + (println total) + (println (length nk)) + (free ks) + (free nk)) + (free names) + (free none)) + 0) diff --git a/test/test_acceptance.ml b/test/test_acceptance.ml index 2ba98fb4..7ff9227d 100644 --- a/test/test_acceptance.ml +++ b/test/test_acceptance.ml @@ -4422,6 +4422,8 @@ level "1" in outputs "map iteration" "programs/map-iter.flan" map_iter_out; outputs ~opt:"-O0" "map iteration, -O0" "programs/map-iter.flan" map_iter_out; + outputs "map-keys and map-values" "programs/map-keys.flan" + "10 20 30 \n100 200 300 \n3\n0\n"; (* Removal, which is the operation that can break the others. A probe stops at the first group holding an empty slot, so a slot emptied in