map-keys and map-values are prelude generics over any hashable key

This commit is contained in:
Joseph Ferano 2026-09-25 15:17:05 +07:00
parent 52c154e306
commit 6745aa7cb4
5 changed files with 61 additions and 17 deletions

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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)

View File

@ -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