core: move frequencies/group-by/not-empty/filterv/max-key/min-key to Clojure

New collection tier (core/20-coll.clj): six pure, eager fns expressed as
compositions of frozen primitives (reduce/assoc/get/conj/filter/vec). Six more
core-* primitives + table entries gone from the Janet kernel.

Two semantics to preserve, caught by the suite:
- max-key/min-key use the canonical multi-arity form (strict </> on the first
  pair, <=/>= in the fold) to reproduce the JVM IEEE-754 NaN behavior; a single
  reduce got the NaN cases wrong.
- frequencies/group-by base on (hash-map), not the {} literal: a struct map
  doesn't canonicalize collection keys across representations ({:a 1} literal vs
  (hash-map :a 1)), a PHM does — same reason the Janet impl used make-phm.

Conformance 218/218 all modes; battery holds 3916.
This commit is contained in:
Yogthos 2026-06-06 18:02:34 -04:00
parent 279c8e176b
commit 57b8ffcb9b
3 changed files with 60 additions and 51 deletions

View file

@ -0,0 +1,53 @@
;; clojure.core — collection tier. Pure, eager fns expressed as compositions of
;; already-frozen core primitives (reduce/assoc/get/conj/filter/vec/count/>=).
;; No host internals, no laziness, no macros — so they compile cleanly and stay
;; redefinable. Loaded after the seq tier; self-hosted in compile mode.
;;
;; Same migration rule as the seq tier (see 10-seq.clj): not in core-renames, no
;; internal Janet callers, not used by the self-hosted compiler.
;; Base is (hash-map), not the {} literal: a literal map is a struct that doesn't
;; canonicalize collection keys across representations (a {:a 1} literal vs
;; (hash-map :a 1) key), whereas a PHM does — so counting/grouping by collection
;; value needs the PHM base (the prior Janet impl used make-phm for this reason).
(defn frequencies [coll]
(reduce (fn [counts x] (assoc counts x (inc (get counts x 0)))) (hash-map) coll))
(defn group-by [f coll]
(reduce (fn [ret x] (let [k (f x)] (assoc ret k (conj (get ret k []) x)))) (hash-map) coll))
(defn not-empty [coll]
(if (or (nil? coll) (zero? (count coll))) nil coll))
(defn filterv [pred coll]
(vec (filter pred coll)))
;; Greatest/least x by (k x). Canonical Clojure multi-arity: the first pair uses
;; strict < / > and the fold uses <= / >= — this exact ordering reproduces the
;; JVM IEEE-754 NaN behavior (e.g. (min-key identity 1 ##NaN) => ##NaN). > / <
;; throw on non-numbers, as Clojure does.
(defn max-key
([k x] x)
([k x y] (if (> (k x) (k y)) x y))
([k x y & more]
(let [kx (k x) ky (k y)
v (if (> kx ky) x y)
kv (if (> kx ky) kx ky)]
(loop [v v kv kv more more]
(if (seq more)
(let [w (first more) kw (k w)]
(if (>= kw kv) (recur w kw (next more)) (recur v kv (next more))))
v)))))
(defn min-key
([k x] x)
([k x y] (if (< (k x) (k y)) x y))
([k x y & more]
(let [kx (k x) ky (k y)
v (if (< kx ky) x y)
kv (if (< kx ky) kx ky)]
(loop [v v kv kv more more]
(if (seq more)
(let [w (first more) kw (k w)]
(if (<= kw kv) (recur w kw (next more)) (recur v kv (next more))))
v)))))

View file

@ -38,7 +38,8 @@
# lazily on the first such form, sees the kernel tier already in place). # lazily on the first such form, sees the kernel tier already in place).
(def- core-tiers (def- core-tiers
[{:ns "clojure.core.00-kernel" :kernel true} [{:ns "clojure.core.00-kernel" :kernel true}
{:ns "clojure.core.10-seq" :kernel false}]) {:ns "clojure.core.10-seq" :kernel false}
{:ns "clojure.core.20-coll" :kernel false}])
(defn- eval-overlay-source [ctx src] (defn- eval-overlay-source [ctx src]
(var s src) (var s src)

View file

@ -1166,21 +1166,8 @@
(do (put seen x true) (array/push result x)))) (do (put seen x true) (array/push result x))))
(if (jvec? coll) (make-vec result) result))))) (if (jvec? coll) (make-vec result) result)))))
(defn core-group-by [f coll] # group-by / frequencies now live in the Clojure collection tier
(def f (as-fn f)) # (core/20-coll.clj).
# phm base so collection keys group by value
(var result (make-phm))
(each x (realize-for-iteration coll)
(let [k (f x)]
(set result (phm-assoc result k (array/push (phm-get result k @[]) x)))))
result)
(defn core-frequencies [coll]
# phm base so collection elements are counted by value
(var result (make-phm))
(each x (realize-for-iteration coll)
(set result (phm-assoc result x (+ 1 (phm-get result x 0)))))
result)
(defn core-partition (defn core-partition
"(partition n coll) or (partition n step coll). Only complete partitions of "(partition n coll) or (partition n step coll). Only complete partitions of
@ -3071,10 +3058,7 @@
(when (not (number? n)) (error "nthnext requires a numeric count")) (when (not (number? n)) (error "nthnext requires a numeric count"))
(let [r (core-nthrest coll n)] (if (or (nil? r) (= 0 (length r))) nil r))) (let [r (core-nthrest coll n)] (if (or (nil? r) (= 0 (length r))) nil r)))
(defn core-filterv [pred coll] # filterv now lives in the Clojure collection tier (core/20-coll.clj).
(def pred (as-fn pred))
(let [r @[]] (each x (realize-for-iteration coll) (when (truthy? (pred x)) (array/push r x)))
(make-vec r)))
# mapv lives in the Clojure kernel tier — core/00-kernel.clj. # mapv lives in the Clojure kernel tier — core/00-kernel.clj.
@ -3143,8 +3127,7 @@
(table? coll) @{} (table? coll) @{}
nil)) nil))
(defn core-not-empty [coll] # not-empty now lives in the Clojure collection tier (core/20-coll.clj).
(if (or (nil? coll) (= 0 (core-count coll))) nil coll))
# rseq is defined only on vectors and sorted collections (Reversible). # rseq is defined only on vectors and sorted collections (Reversible).
(defn core-rseq [coll] (defn core-rseq [coll]
@ -3199,29 +3182,7 @@
# asymmetry reproduces the JVM's NaN-ordering behavior. Janet's < / > are used # asymmetry reproduces the JVM's NaN-ordering behavior. Janet's < / > are used
# directly (NaN comparisons are false, never throwing). # directly (NaN comparisons are false, never throwing).
# keys must be numbers (NaN allowed) — like Clojure, which compares them with </>. # keys must be numbers (NaN allowed) — like Clojure, which compares them with </>.
(defn core-min-key [f & xs] # min-key / max-key now live in the Clojure collection tier (core/20-coll.clj).
(def f (as-fn f))
(when (= 0 (length xs)) (error "min-key requires at least one value"))
(if (= 1 (length xs)) (first xs)
(do (var v (in xs 0)) (var kv (need-num (f v) "min-key"))
(let [y (in xs 1) ky (need-num (f y) "min-key")] (when (not (< kv ky)) (set v y) (set kv ky)))
(var i 2)
(while (< i (length xs))
(let [w (in xs i) kw (need-num (f w) "min-key")] (when (<= kw kv) (set v w) (set kv kw)))
(++ i))
v)))
(defn core-max-key [f & xs]
(def f (as-fn f))
(when (= 0 (length xs)) (error "max-key requires at least one value"))
(if (= 1 (length xs)) (first xs)
(do (var v (in xs 0)) (var kv (need-num (f v) "max-key"))
(let [y (in xs 1) ky (need-num (f y) "max-key")] (when (not (> kv ky)) (set v y) (set kv ky)))
(var i 2)
(while (< i (length xs))
(let [w (in xs i) kw (need-num (f w) "max-key")] (when (>= kw kv) (set v w) (set kv kw)))
(++ i))
v)))
(defn core-not-every? [pred coll] (defn core-not-every? [pred coll]
(def pred (as-fn pred)) (def pred (as-fn pred))
@ -3791,9 +3752,7 @@
"take-nth" core-take-nth "take-nth" core-take-nth
"nthrest" core-nthrest "nthrest" core-nthrest
"nthnext" core-nthnext "nthnext" core-nthnext
"filterv" core-filterv
"empty" core-empty "empty" core-empty
"not-empty" core-not-empty
"rseq" core-rseq "rseq" core-rseq
"shuffle" core-shuffle "shuffle" core-shuffle
"replace" core-replace "replace" core-replace
@ -3803,8 +3762,6 @@
"ifn?" core-ifn? "ifn?" core-ifn?
"indexed?" core-indexed? "indexed?" core-indexed?
"distinct?" core-distinct? "distinct?" core-distinct?
"min-key" core-min-key
"max-key" core-max-key
"not-every?" core-not-every? "not-every?" core-not-every?
"not-any?" core-not-any? "not-any?" core-not-any?
"vary-meta" core-vary-meta "vary-meta" core-vary-meta
@ -3829,8 +3786,6 @@
"sort" core-sort "sort" core-sort
"sort-by" core-sort-by "sort-by" core-sort-by
"distinct" core-distinct "distinct" core-distinct
"group-by" core-group-by
"frequencies" core-frequencies
"partition" core-partition "partition" core-partition
"partition-by" core-partition-by "partition-by" core-partition-by
"range" core-range "range" core-range