core: Stage 3 — leaf batch 2: sixteen more seed fns to the overlay; retire MIGRATION.md
key/val/select-keys/zipmap/merge/merge-with/get-in/memoize/partial/ trampoline/some?/true?/false?/max/min/reverse move to 20-coll.clj as the canonical Clojure definitions, plus find — which was previously missing from jolt entirely (select-keys/merge-with/memoize build on it). Two behavior fixes ride along: memoize now caches nil results (the kernel fn re-computed them — canonical find-based impl), and conj of nil onto a map is a no-op as in Clojure (it errored; the canonical merge relies on it). max/min keep the JVM NaN behavior by construction (pairwise >/<). not= stays: the kernel tier (subvec) uses it. One new tier-ordering rule, learned the hard way: a tier may only use macros from tiers that load BEFORE it — memoize's if-let (30-macros) broke compiled init while interpret mode passed, because compile expands macros at tier load and the interpreter expands lazily. Now documented in the migration workflow note. MIGRATION.md is gone — task tracking lives in beads (jolt-ded; the per-batch workflow, tier-order rules, perf wall, and remaining candidates are in bd memory core-migration-workflow). The doc's candidate lists had gone stale against the actual seed anyway. 43 new spec rows. Gate green: conformance 326x3, suite >= baseline, full jpm test, bench at parity with main back-to-back (4851 vs 4831 TOTAL).
This commit is contained in:
parent
af34d94870
commit
0e71b193e5
4 changed files with 175 additions and 266 deletions
|
|
@ -630,3 +630,105 @@
|
||||||
[v]
|
[v]
|
||||||
(let [t (:test (meta v))]
|
(let [t (:test (meta v))]
|
||||||
(if t (do (t) :ok) :no-test)))
|
(if t (do (t) :ok) :no-test)))
|
||||||
|
|
||||||
|
;; --- Phase 2 leaf batch 2 (jolt-ded): canonical Clojure ports ----------------
|
||||||
|
;; key/val/find first — merge-with and memoize below use them.
|
||||||
|
|
||||||
|
;; An entry is any 2-element vector in jolt ((seq m) yields tuples, find
|
||||||
|
;; builds a pvec — both count as entries; Clojure's stricter MapEntry-only
|
||||||
|
;; key/val has no analog here).
|
||||||
|
(defn- map-entryish? [e] (and (vector? e) (= 2 (count e))))
|
||||||
|
(defn key [e] (if (map-entryish? e) (nth e 0) (throw (ex-info "key requires a map entry" {}))))
|
||||||
|
(defn val [e] (if (map-entryish? e) (nth e 1) (throw (ex-info "val requires a map entry" {}))))
|
||||||
|
|
||||||
|
;; find was previously missing from jolt entirely. Presence (contains?), not
|
||||||
|
;; value, decides — so (find {:a nil} :a) is [:a nil]. Works on vectors by index.
|
||||||
|
(defn find [m k]
|
||||||
|
(when (contains? m k) [k (get m k)]))
|
||||||
|
|
||||||
|
(defn some? [x] (not (nil? x)))
|
||||||
|
(defn true? [x] (= true x))
|
||||||
|
(defn false? [x] (= false x))
|
||||||
|
|
||||||
|
;; Presence-preserving: a key with a nil value is kept ((hash-map) base keeps
|
||||||
|
;; nil values and canonicalizes collection keys).
|
||||||
|
(defn select-keys [map keyseq]
|
||||||
|
(reduce (fn [m k] (if (contains? map k) (assoc m k (get map k)) m))
|
||||||
|
(hash-map) keyseq))
|
||||||
|
|
||||||
|
(defn zipmap [keys vals]
|
||||||
|
(loop [m (hash-map) ks (seq keys) vs (seq vals)]
|
||||||
|
(if (and ks vs)
|
||||||
|
(recur (assoc m (first ks) (first vs)) (next ks) (next vs))
|
||||||
|
m)))
|
||||||
|
|
||||||
|
;; conj semantics per entry arg (a map merges, a [k v] pair adds); nil args are
|
||||||
|
;; no-ops; all-nil (or no args) is nil.
|
||||||
|
(defn merge [& maps]
|
||||||
|
(when (some identity maps)
|
||||||
|
(reduce (fn [acc m] (if (nil? m) acc (conj (or acc (hash-map)) m)))
|
||||||
|
maps)))
|
||||||
|
|
||||||
|
(defn merge-with [f & maps]
|
||||||
|
(when (some identity maps)
|
||||||
|
(let [merge-entry (fn [m e]
|
||||||
|
(let [k (key e) v (val e)]
|
||||||
|
;; presence — not nil-of-value — decides combination
|
||||||
|
(if (contains? m k)
|
||||||
|
(assoc m k (f (get m k) v))
|
||||||
|
(assoc m k v))))
|
||||||
|
merge2 (fn [m1 m2]
|
||||||
|
(reduce merge-entry (or m1 (hash-map)) (seq m2)))]
|
||||||
|
(reduce merge2 maps))))
|
||||||
|
|
||||||
|
(defn get-in
|
||||||
|
([m ks] (reduce get m ks))
|
||||||
|
([m ks not-found]
|
||||||
|
;; a fresh table is its own identity — a present-but-nil step is
|
||||||
|
;; distinguished from a missing one
|
||||||
|
(let [sentinel (hash-map)]
|
||||||
|
(loop [m m ks (seq ks)]
|
||||||
|
(if ks
|
||||||
|
(let [nxt (get m (first ks) sentinel)]
|
||||||
|
(if (identical? sentinel nxt)
|
||||||
|
not-found
|
||||||
|
(recur nxt (next ks))))
|
||||||
|
m)))))
|
||||||
|
|
||||||
|
;; find-based, so nil RESULTS are cached too (the old kernel fn re-computed
|
||||||
|
;; them); args canonicalize as a collection key.
|
||||||
|
(defn memoize [f]
|
||||||
|
(let [mem (atom (hash-map))]
|
||||||
|
(fn [& args]
|
||||||
|
;; plain let/if, not if-let: this tier loads before 30-macros defines it
|
||||||
|
(let [e (find (deref mem) args)]
|
||||||
|
(if e
|
||||||
|
(val e)
|
||||||
|
(let [ret (apply f args)]
|
||||||
|
(swap! mem assoc args ret)
|
||||||
|
ret))))))
|
||||||
|
|
||||||
|
(defn partial
|
||||||
|
([f] f)
|
||||||
|
([f a] (fn [& args] (apply f a args)))
|
||||||
|
([f a b] (fn [& args] (apply f a b args)))
|
||||||
|
([f a b c] (fn [& args] (apply f a b c args)))
|
||||||
|
([f a b c & more] (fn [& args] (apply f a b c (concat more args)))))
|
||||||
|
|
||||||
|
(defn trampoline
|
||||||
|
([f] (let [ret (f)] (if (fn? ret) (trampoline ret) ret)))
|
||||||
|
([f & args] (trampoline (fn [] (apply f args)))))
|
||||||
|
|
||||||
|
;; Canonical pairwise max/min: > / < throw on non-numbers, and the NaN
|
||||||
|
;; behavior is Clojure's by construction.
|
||||||
|
(defn max
|
||||||
|
([x] x)
|
||||||
|
([x y] (if (> x y) x y))
|
||||||
|
([x y & more] (reduce max (max x y) more)))
|
||||||
|
|
||||||
|
(defn min
|
||||||
|
([x] x)
|
||||||
|
([x y] (if (< x y) x y))
|
||||||
|
([x y & more] (reduce min (min x y) more)))
|
||||||
|
|
||||||
|
(defn reverse [coll] (reduce conj (list) coll))
|
||||||
|
|
|
||||||
|
|
@ -1,120 +0,0 @@
|
||||||
# clojure.core migration worklist (jolt-1j0)
|
|
||||||
|
|
||||||
Tracking the move of clojure.core from native Janet (`src/jolt/core.janet`,
|
|
||||||
4145 lines / 421 `core-*` fns) into the self-hosted Clojure overlay
|
|
||||||
(`jolt-core/clojure/core/`). Goal: shrink the Janet seed to `core-renames` +
|
|
||||||
genuinely host-coupled fns.
|
|
||||||
|
|
||||||
## Phase 0 classification (heuristic — validate per batch)
|
|
||||||
|
|
||||||
| Bucket | Count | Disposition |
|
|
||||||
|---|---|---|
|
|
||||||
| SEED (in `compiler/core-renames`) | 73 | stay in Janet (compiler emits `core-X` directly) |
|
|
||||||
| MACRO (in `core-macro-names`) | 44 | Phase 3 |
|
|
||||||
| HOST-coupled (atoms/vars/meta/proxy/transient/arrays/futures/ns/io) | 80 | Phase 4 (where feasible) / stay |
|
|
||||||
| LAZY-coupled | 28 | Phase 5 |
|
|
||||||
| MOVABLE pure-eager (candidates) | 193 | **Phase 2** |
|
|
||||||
|
|
||||||
Counts are heuristic (name + body markers); the MOVABLE list still has some
|
|
||||||
host/lazy leakage (e.g. transient `assoc!`/`conj!`, `doall`/`dorun`,
|
|
||||||
`chunk-*`, `deliver`) to filter out as each batch is actually moved.
|
|
||||||
|
|
||||||
**Key finding:** after removing SEED + HOST, the self-hosted compiler
|
|
||||||
(`jolt-core/jolt/{ir,analyzer}.clj`) uses **no** additional clojure.core fns
|
|
||||||
beyond the kernel tier (`second`/`peek`/`subvec`/`mapv`/`update`) plus host
|
|
||||||
primitives (`atom`/`swap!`/`reset!`). So **Phase 1 (compiler-dep kernel tier)
|
|
||||||
is essentially already complete** — to verify, not build.
|
|
||||||
|
|
||||||
## Performance baseline (test/bench/core-bench.janet, compile mode, min of 5, ms)
|
|
||||||
|
|
||||||
| bench | ms |
|
|
||||||
|---|---|
|
|
||||||
| fib | 128 |
|
|
||||||
| seq-pipe | 88 |
|
|
||||||
| reduce | 391 |
|
|
||||||
| into-vec | 194 |
|
|
||||||
| map-build | 681 |
|
|
||||||
| map-read | 6 |
|
|
||||||
| str-join | 244 |
|
|
||||||
| hof | 604 |
|
|
||||||
| **TOTAL** | **2336** |
|
|
||||||
|
|
||||||
Re-run after each phase; watch for regressions as fns move from native Janet to
|
|
||||||
self-hosted Clojure (interpreted/compiled, slower than native primitives).
|
|
||||||
|
|
||||||
## Per-batch workflow + gate (every migration step)
|
|
||||||
1. Canonical Clojure def in the overlay tier; remove the Janet `core-X` defn +
|
|
||||||
its `core-bindings` entry (confirm leaf first: only defn+binding refs).
|
|
||||||
2. **Add regression tests** for each moved fn — spec cases (test/spec/*-spec.janet,
|
|
||||||
interpret) and, for any fn whose behavior is subtle or was buggy, a case in the
|
|
||||||
3-mode conformance set (test/integration/conformance-test.janet).
|
|
||||||
3. Gate: conformance ×3 modes · clojure-test-suite ≥ baseline · stage2==stage3
|
|
||||||
fixpoint · fib compiled-fast · core-bench A/B under identical load (the
|
|
||||||
absolute number is load-sensitive — compare batch-vs-prior back to back).
|
|
||||||
|
|
||||||
If a moved fn surfaces a latent bug (e.g. nthrest's nil-vs-() result, the
|
|
||||||
if-let/when-let else-scope leak), fix it to match Clojure and add a regression
|
|
||||||
test, rather than preserving the bug.
|
|
||||||
|
|
||||||
## Phase 2 batches landed
|
|
||||||
|
|
||||||
- **Sorted collections (jolt-0lj)** — sorted-map / sorted-map-by / sorted-set /
|
|
||||||
sorted-set-by / sorted? / sorted-map? / sorted-set? / subseq / rsubseq are
|
|
||||||
pure Clojure in their own tier (`25-sorted.clj`). Representation: tagged host
|
|
||||||
table with a comparator-ordered :entries vector and the ops ATTACHED to the
|
|
||||||
value (:ops map) — the seed dispatch branches call through (coll :ops), so no
|
|
||||||
module-level hooks and the ops survive forks/AOT images. Host surface grew by
|
|
||||||
two minimal value primitives: jolt.host/tagged-table and jolt.host/ref-get
|
|
||||||
(raw field read — plain `get` on a sorted coll is the comparator lookup and
|
|
||||||
would recurse). GOTCHA for future attached-ops ports: inside the overlay,
|
|
||||||
NEVER read your own wrapper's fields with `get`.
|
|
||||||
|
|
||||||
- **Leaf batch (jolt-ded)** — complement, fnil (canonical 2/3/4-arity: patch
|
|
||||||
the first 1-3 args only, unlike the old patch-everything kernel fn),
|
|
||||||
clojure-version, bigdec, numerator, denominator, supers, munge, test moved
|
|
||||||
to 20-coll.clj.
|
|
||||||
- ***in* reader family (jolt-0d9)** — *in*, read-line, read, with-in-str,
|
|
||||||
line-seq in a new `50-io.clj` tier over two seed seams (__stdin-read-line,
|
|
||||||
__parse-next). GOTCHAS: a map LITERAL with :jolt/type as a key parses as a
|
|
||||||
tagged form (don't tag overlay value maps); a leftover seed stub holding the
|
|
||||||
same name breaks direct-linked overlay self-recursion (line-seq bound to the
|
|
||||||
stub's root and truncated after one element) — delete the stub first.
|
|
||||||
|
|
||||||
## MOVABLE candidates (Phase 2 worklist, 193)
|
|
||||||
>Eduction NaN? abs aclone alength ancestors array-map array-seq assoc! associative? bean bigdec bigint biginteger boolean boolean? booleans byte bytes bytes? cat char char-escape-string char-name-string char? chars chunk chunk-append chunk-buffer chunk-cons chunk-first chunk-next chunk-rest chunked-seq? class clojure-version comparator compare-and-set! completing conj! counted? decimal? deliver denominator derive descendants destructure disj disj! dissoc! distinct? doall dorun double? doubles drop-last eduction empty ensure-reduced enumeration-seq ex-cause ex-data ex-info ex-info? ex-message find float? floats force halt-when hash-combine hash-map hash-ordered-coll hash-set hash-unordered-coll ident? ifn? indexed? infinite? inst-ms inst? integer? ints isa? iterator-seq key keyword keyword-identical? list* list? longs macrofy map-entry? memfn munge nat-int? neg-int? not-any? not-every? nthnext nthrest numerator numeric= object? parents persistent! pop pop! pos-int? pr prefers println-str prn-str promise qualified-ident? qualified-keyword? qualified-symbol? rand rand-nth random-sample ratio? rational? rationalize re-groups re-matcher record? reduce-kv reduced reduced? reductions replace replicate resolve reversible? rseq rsubseq run! seq-to-map-for-destructuring seque set set? short shorts shuffle simple-ident? simple-keyword? simple-symbol? some-search sort sort-by sorted-map sorted-map-by sorted-map? sorted-set sorted-set-by sorted-set? special-symbol? split-at split-with str-join str-replace-all str-replace-first str-split subseq supers symbol tagged-literal tagged-literal? take-last test transduce unchecked-add unchecked-byte unchecked-char unchecked-dec unchecked-divide-int unchecked-double unchecked-float unchecked-inc unchecked-int unchecked-multiply unchecked-negate unchecked-remainder-int unchecked-short unchecked-subtract undefined? underive uri? uuid? val vector volatile! volatile? xml-seq
|
|
||||||
|
|
||||||
## HOST-coupled (Phase 4 / stay, 80)
|
|
||||||
add-watch aget alter-meta! alter-var-root aset aset-boolean aset-byte aset-char aset-double aset-float aset-int aset-long aset-short atom atom? avoid-method-too-large boolean-array bounded-count byte-array char-array construct-proxy copy-core-var copy-var delay? deref double-array float-array future-call future-cancel future-cancelled? future-done? future? get-proxy-class get-validator init-proxy int-array intern into-array long-array make-array make-delay meta namespace namespace-munge new-var ns-name object-array pop-thread-bindings prefer-method print-dup print-method print-str proxy-call-with-super proxy-mappings proxy-super push-thread-bindings reader-conditional reader-conditional? remove-watch reset! reset-meta! reset-vals! set-validator! short-array swap! swap-vals! thread-first thread-last to-array to-array-2d transient transient? update-proxy var-dynamic? var-get var-set var? vary-meta vreset! vswap! with-meta
|
|
||||||
|
|
||||||
## LAZY-coupled (Phase 5, 28)
|
|
||||||
concat cycle dedupe distinct flatten interleave interpose iterate keep keep-indexed line-seq macro-names map-indexed mapcat partition partition-all partition-by rand-int random-uuid realized? repeat repeatedly seqable? sequence sequential? take-nth trampoline tree-seq unreduced
|
|
||||||
|
|
||||||
## Phase 3 (macros) — status & findings
|
|
||||||
|
|
||||||
20 macros moved to the overlay: 19 user-facing in `30-macros.clj`, plus `when`
|
|
||||||
in a new `00-syntax.clj` tier loaded **before** the kernel (interpreted defmacros,
|
|
||||||
so the macros exist before any code that uses them compiles).
|
|
||||||
|
|
||||||
Macro-authoring toolkit for jolt (learned the hard way):
|
|
||||||
- single-template hygiene: auto-gensym `foo#`
|
|
||||||
- shared explicit fresh symbol: `(symbol (str (gensym)))` — a bare `(gensym)` in a
|
|
||||||
macro body returns a *Janet* symbol the destructurer rejects
|
|
||||||
- let-rebinding: splice binding *pairs* into a TEMPLATE vector (`[~a ~b ~@pairs]`),
|
|
||||||
not a pre-built pvec value — `core-let` wants a tuple form
|
|
||||||
- build sub-forms via templates, never `cons`/`list` (those make plists the
|
|
||||||
evaluator can't run as a form)
|
|
||||||
- Jolt `defmacro` is **single-arity** — use `& rest`/destructuring
|
|
||||||
- syntax-tier macros may use only special forms + core-renames seed primitives
|
|
||||||
|
|
||||||
**Performance wall (the hot macros stay in Janet for now):** the load-order story
|
|
||||||
works, but moving the *hot* fundamental control macros (`and`/`or`/`cond`/
|
|
||||||
`when-not`) regressed the battery — as interpreted overlay defmacros they expand
|
|
||||||
slower than native Janet, and since they appear in nearly every form the
|
|
||||||
cumulative overhead tipped a heavy suite file over the 6 s per-file timeout
|
|
||||||
(3930 -> 3911, +1 timeout). They are correct (conformance 228×3, all edge cases),
|
|
||||||
but reverted. Moving `and/or/cond/when-not/case/doseq/declare/cond->/->/->>`
|
|
||||||
needs a **fast (compiled) macro-expansion path**, not interpreted defmacros.
|
|
||||||
|
|
||||||
Deferred: `defn/defn-/fn/let/loop` (fundamental + same speed concern), the type
|
|
||||||
machinery (`defrecord/defprotocol/extend-*/reify/proxy/definterface` → Phase 4),
|
|
||||||
`lazy-seq/lazy-cat` (→ Phase 5).
|
|
||||||
|
|
@ -177,7 +177,7 @@
|
||||||
|
|
||||||
(defn core-nil? [x] (nil? x))
|
(defn core-nil? [x] (nil? x))
|
||||||
(defn core-not [x] (if x false true))
|
(defn core-not [x] (if x false true))
|
||||||
(defn core-some? [x] (not (nil? x)))
|
# some? / true? / false? now live in the Clojure collection tier.
|
||||||
(defn core-string? [x] (string? x))
|
(defn core-string? [x] (string? x))
|
||||||
(defn core-number? [x] (number? x))
|
(defn core-number? [x] (number? x))
|
||||||
(defn core-fn? [x] (or (function? x) (cfunction? x)))
|
(defn core-fn? [x] (or (function? x) (cfunction? x)))
|
||||||
|
|
@ -206,8 +206,8 @@
|
||||||
(= :jolt/sorted-map (get x :jolt/type))
|
(= :jolt/sorted-map (get x :jolt/type))
|
||||||
(= :jolt/sorted-set (get x :jolt/type))))))
|
(= :jolt/sorted-set (get x :jolt/type))))))
|
||||||
|
|
||||||
(defn core-true? [x] (= true x))
|
|
||||||
(defn core-false? [x] (= false x))
|
|
||||||
(defn core-identical? [a b] (= a b))
|
(defn core-identical? [a b] (= a b))
|
||||||
|
|
||||||
# Strictness helpers: like Clojure, numeric ops reject non-numbers, and the
|
# Strictness helpers: like Clojure, numeric ops reject non-numbers, and the
|
||||||
|
|
@ -304,8 +304,9 @@
|
||||||
(let [m (core-rem n d)]
|
(let [m (core-rem n d)]
|
||||||
(if (or (= m 0) (= (> n 0) (> d 0))) m (+ m d)))))
|
(if (or (= m 0) (= (> n 0) (> d 0))) m (+ m d)))))
|
||||||
|
|
||||||
(defn core-max [& args] (each x args (need-num x "max")) (apply max args))
|
# max / min now live in the Clojure collection tier (canonical pairwise
|
||||||
(defn core-min [& args] (each x args (need-num x "min")) (apply min args))
|
# >/<, so non-numbers throw and NaN behaves as on the JVM).
|
||||||
|
|
||||||
|
|
||||||
(defn core-rand [& n] (let [r (math/random)] (if (empty? n) r (* r (in n 0)))))
|
(defn core-rand [& n] (let [r (math/random)] (if (empty? n) r (* r (in n 0)))))
|
||||||
(defn core-rand-int [n] (math/floor (* (math/random) n)))
|
(defn core-rand-int [n] (math/floor (* (math/random) n)))
|
||||||
|
|
@ -466,7 +467,10 @@
|
||||||
(do
|
(do
|
||||||
(var result coll)
|
(var result coll)
|
||||||
(each x xs
|
(each x xs
|
||||||
(if (map-value? x)
|
(cond
|
||||||
|
# conj nil onto a map is a no-op (Clojure)
|
||||||
|
(nil? x) nil
|
||||||
|
(map-value? x)
|
||||||
# conj a map -> merge its entries
|
# conj a map -> merge its entries
|
||||||
(each e (map-entries-of x)
|
(each e (map-entries-of x)
|
||||||
(set result (phm-assoc result (in e 0) (in e 1))))
|
(set result (phm-assoc result (in e 0) (in e 1))))
|
||||||
|
|
@ -475,7 +479,9 @@
|
||||||
(do
|
(do
|
||||||
(var result coll)
|
(var result coll)
|
||||||
(each x xs
|
(each x xs
|
||||||
(if (map-value? x)
|
(cond
|
||||||
|
(nil? x) nil
|
||||||
|
(map-value? x)
|
||||||
(each e (map-entries-of x)
|
(each e (map-entries-of x)
|
||||||
(set result (map-assoc1 result (in e 0) (in e 1))))
|
(set result (map-assoc1 result (in e 0) (in e 1))))
|
||||||
(set result (map-assoc1 result (vnth x 0) (vnth x 1)))))
|
(set result (map-assoc1 result (vnth x 0) (vnth x 1)))))
|
||||||
|
|
@ -604,20 +610,7 @@
|
||||||
(realize-for-iteration t))]
|
(realize-for-iteration t))]
|
||||||
(jolt-call f ;fixed ;tail)))))
|
(jolt-call f ;fixed ;tail)))))
|
||||||
|
|
||||||
(defn core-get-in [m ks &opt default]
|
# get-in now lives in the Clojure collection tier (core/20-coll.clj).
|
||||||
(default default nil)
|
|
||||||
(def ks (vview ks))
|
|
||||||
# Walk with a fresh sentinel so a PRESENT key whose value is nil is distinguished
|
|
||||||
# from a missing key: only a genuinely-absent step falls back to default.
|
|
||||||
(def absent @{})
|
|
||||||
(var current m)
|
|
||||||
(var i 0)
|
|
||||||
(var missing false)
|
|
||||||
(while (< i (length ks))
|
|
||||||
(let [nxt (core-get current (ks i) absent)]
|
|
||||||
(if (= nxt absent) (do (set missing true) (break)) (set current nxt)))
|
|
||||||
(++ i))
|
|
||||||
(if missing default current))
|
|
||||||
|
|
||||||
(defn core-contains? [coll key]
|
(defn core-contains? [coll key]
|
||||||
(if (core-sorted? coll) (if ((sorted-op coll :contains) coll key) true false)
|
(if (core-sorted? coll) (if ((sorted-op coll :contains) coll key) true false)
|
||||||
|
|
@ -774,59 +767,9 @@
|
||||||
# ((into #{} [:a :b]) was #{}, jolt-h86)
|
# ((into #{} [:a :b]) was #{}, jolt-h86)
|
||||||
(do (var result to) (each x items (set result (core-conj result x))) result)))
|
(do (var result to) (each x items (set result (core-conj result x))) result)))
|
||||||
|
|
||||||
(defn core-merge [& maps]
|
# merge now lives in the Clojure collection tier (core/20-coll.clj).
|
||||||
# Clojure: (when (some identity maps) (reduce conj (or (first maps) {}) (rest maps)))
|
|
||||||
# - (merge) and (merge nil nil) -> nil; nil args elsewhere are no-ops.
|
|
||||||
# - later args follow conj semantics (a map merges its entries; a [k v]
|
|
||||||
# vector/map-entry adds that entry).
|
|
||||||
(var any false)
|
|
||||||
(each m maps (when (not (nil? m)) (set any true)))
|
|
||||||
(if (not any)
|
|
||||||
nil
|
|
||||||
(do
|
|
||||||
(var result (let [f (in maps 0)] (if (nil? f) (struct) f)))
|
|
||||||
(var i 1)
|
|
||||||
(while (< i (length maps))
|
|
||||||
(let [m (in maps i)]
|
|
||||||
(cond
|
|
||||||
(nil? m) nil
|
|
||||||
(or (phm? m) (struct? m))
|
|
||||||
(each e (map-entries-of m)
|
|
||||||
(set result (core-assoc result (in e 0) (in e 1))))
|
|
||||||
# a [k v] pair (map-entry / 2-vector), per conj
|
|
||||||
(and (or (pvec? m) (tuple? m) (array? m))
|
|
||||||
(= 2 (if (pvec? m) (pv-count m) (length m))))
|
|
||||||
(set result (core-assoc result (vnth m 0) (vnth m 1)))
|
|
||||||
# scalars, sets, and wrong-length sequentials can't merge into a map
|
|
||||||
# (a length-2 vector was handled above; anything else here is bad)
|
|
||||||
(or (number? m) (string? m) (buffer? m) (keyword? m) (boolean? m)
|
|
||||||
(set? m) (plist? m) (pvec? m) (tuple? m) (array? m)
|
|
||||||
(and (struct? m) (get m :jolt/type)))
|
|
||||||
(error (string "Can't merge " (type m) " into a map"))
|
|
||||||
# other map-like tables (records, sorted-maps, host tables): lenient conj
|
|
||||||
(set result (core-conj result m))))
|
|
||||||
(++ i))
|
|
||||||
result)))
|
|
||||||
|
|
||||||
(defn core-merge-with [f & maps]
|
# merge-with now lives in the Clojure collection tier (core/20-coll.clj).
|
||||||
# Presence — not nil-of-value — decides whether to combine: a key present in the
|
|
||||||
# accumulator with a nil value still triggers (f existing v), matching Clojure.
|
|
||||||
(if (= 0 (length maps))
|
|
||||||
nil
|
|
||||||
(do
|
|
||||||
(var result (first maps))
|
|
||||||
(var mi 1)
|
|
||||||
(while (< mi (length maps))
|
|
||||||
(let [m (maps mi)]
|
|
||||||
(when m
|
|
||||||
(each e (map-entries-of m)
|
|
||||||
(let [k (in e 0) v (in e 1)]
|
|
||||||
(set result
|
|
||||||
(if (core-contains? result k)
|
|
||||||
(core-assoc result k (f (core-get result k) v))
|
|
||||||
(core-assoc result k v)))))))
|
|
||||||
(++ mi))
|
|
||||||
result)))
|
|
||||||
|
|
||||||
(defn core-keys [m]
|
(defn core-keys [m]
|
||||||
# phm-entries (not phm-to-struct) so keys mapped to nil values are not dropped.
|
# phm-entries (not phm-to-struct) so keys mapped to nil values are not dropped.
|
||||||
|
|
@ -837,24 +780,9 @@
|
||||||
(if (core-sorted-map? m) ((sorted-op m :vals) m)
|
(if (core-sorted-map? m) ((sorted-op m :vals) m)
|
||||||
(if (phm? m) (tuple ;(map |(in $ 1) (phm-entries m))) (tuple ;(map |(m $) (keys m))))))
|
(if (phm? m) (tuple ;(map |(in $ 1) (phm-entries m))) (tuple ;(map |(m $) (keys m))))))
|
||||||
|
|
||||||
(defn core-select-keys [m ks]
|
# select-keys now lives in the Clojure collection tier (core/20-coll.clj).
|
||||||
# Include a key when it is PRESENT (contains?), even if its value is nil — a
|
|
||||||
# struct/table would drop a nil value, so collect entries and build via kvs->map.
|
|
||||||
(def kvs @[])
|
|
||||||
(each k (realize-for-iteration ks)
|
|
||||||
(when (core-contains? m k)
|
|
||||||
(array/push kvs k) (array/push kvs (core-get m k))))
|
|
||||||
(kvs->map kvs))
|
|
||||||
|
|
||||||
(defn core-zipmap [ks vs]
|
# zipmap now lives in the Clojure collection tier (core/20-coll.clj).
|
||||||
(let [ks (realize-for-iteration ks) vs (realize-for-iteration vs)]
|
|
||||||
# collect pairs, then build once — a nil key/value must survive (kvs->map -> phm)
|
|
||||||
(def kvs @[])
|
|
||||||
(var i 0)
|
|
||||||
(while (and (< i (length ks)) (< i (length vs)))
|
|
||||||
(array/push kvs (in ks i)) (array/push kvs (in vs i))
|
|
||||||
(++ i))
|
|
||||||
(kvs->map kvs)))
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Transducers
|
# Transducers
|
||||||
|
|
@ -1304,29 +1232,7 @@
|
||||||
(if ok hit nil)))
|
(if ok hit nil)))
|
||||||
(make-lazy-seq (step init-cs nil)))))
|
(make-lazy-seq (step init-cs nil)))))
|
||||||
|
|
||||||
(defn core-reverse [coll]
|
# reverse now lives in the Clojure collection tier ((reduce conj () coll)).
|
||||||
(if (nil? coll) @[]
|
|
||||||
(if (lazy-seq? coll)
|
|
||||||
(do
|
|
||||||
(var result @[])
|
|
||||||
(var cur coll)
|
|
||||||
# seq-done?, not (nil? (ls-first)): a nil element must not end the walk.
|
|
||||||
(while (not (seq-done? cur))
|
|
||||||
(array/push result (core-first cur))
|
|
||||||
(set cur (core-rest cur)))
|
|
||||||
(var reversed @[])
|
|
||||||
(var i (dec (length result)))
|
|
||||||
(while (>= i 0)
|
|
||||||
(array/push reversed (in result i))
|
|
||||||
(-- i))
|
|
||||||
reversed)
|
|
||||||
(let [c (realize-for-iteration coll)]
|
|
||||||
(var result @[])
|
|
||||||
(var i (dec (length c)))
|
|
||||||
(while (>= i 0)
|
|
||||||
(array/push result (in c i))
|
|
||||||
(-- i))
|
|
||||||
result))))
|
|
||||||
|
|
||||||
(defn core-nth
|
(defn core-nth
|
||||||
"Return the nth element of a sequential collection. With a not-found arg, return
|
"Return the nth element of a sequential collection. With a not-found arg, return
|
||||||
|
|
@ -1445,10 +1351,7 @@
|
||||||
# tier — core/00-kernel.clj.)
|
# tier — core/00-kernel.clj.)
|
||||||
# subvec lives in the Clojure kernel tier — core/00-kernel.clj.
|
# subvec lives in the Clojure kernel tier — core/00-kernel.clj.
|
||||||
|
|
||||||
(defn core-trampoline [f & args]
|
# trampoline now lives in the Clojure collection tier (core/20-coll.clj).
|
||||||
(var result (apply f args))
|
|
||||||
(while (function? result) (set result (result)))
|
|
||||||
result)
|
|
||||||
|
|
||||||
(def core-format (fn [fmt & args] (string/format fmt ;args)))
|
(def core-format (fn [fmt & args] (string/format fmt ;args)))
|
||||||
|
|
||||||
|
|
@ -1523,20 +1426,12 @@
|
||||||
(-- i))
|
(-- i))
|
||||||
(f result))))))
|
(f result))))))
|
||||||
|
|
||||||
(defn core-partial [f & args]
|
# partial now lives in the Clojure collection tier (canonical arities).
|
||||||
(fn [& more] (apply f (array/concat (array/slice args) more))))
|
|
||||||
|
|
||||||
# juxt now lives in the Clojure collection tier (core/20-coll.clj).
|
# juxt now lives in the Clojure collection tier (core/20-coll.clj).
|
||||||
|
|
||||||
(defn core-memoize [f]
|
# memoize now lives in the Clojure collection tier — find-based, so it
|
||||||
(var cache @{})
|
# caches nil results too (this kernel fn re-computed them).
|
||||||
(fn [& args]
|
|
||||||
(let [key (tuple ;args)]
|
|
||||||
(if-let [v (get cache key)]
|
|
||||||
v
|
|
||||||
(let [result (apply f args)]
|
|
||||||
(put cache key result)
|
|
||||||
result)))))
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Collection constructors
|
# Collection constructors
|
||||||
|
|
@ -2616,8 +2511,8 @@
|
||||||
# lists are arrays. So key/val/map-entry? accept a 2-tuple and reject a plain
|
# lists are arrays. So key/val/map-entry? accept a 2-tuple and reject a plain
|
||||||
# vector, matching Clojure (where a MapEntry is distinct from a vector).
|
# vector, matching Clojure (where a MapEntry is distinct from a vector).
|
||||||
(defn- entry-like? [x] (and (tuple? x) (= 2 (length x))))
|
(defn- entry-like? [x] (and (tuple? x) (= 2 (length x))))
|
||||||
(defn core-key [e] (if (entry-like? e) (in e 0) (error "key requires a map entry")))
|
# key / val now live in the Clojure collection tier (core/20-coll.clj),
|
||||||
(defn core-val [e] (if (entry-like? e) (in e 1) (error "val requires a map entry")))
|
# along with find (previously missing from jolt entirely).
|
||||||
(defn core-map-entry? [x] (entry-like? x))
|
(defn core-map-entry? [x] (entry-like? x))
|
||||||
|
|
||||||
# Reversible (supports rseq) = vectors and sorted collections.
|
# Reversible (supports rseq) = vectors and sorted collections.
|
||||||
|
|
@ -2818,7 +2713,6 @@
|
||||||
(def- core-bindings
|
(def- core-bindings
|
||||||
"Map of symbol name → function for all core functions."
|
"Map of symbol name → function for all core functions."
|
||||||
@{"nil?" core-nil?
|
@{"nil?" core-nil?
|
||||||
"some?" core-some?
|
|
||||||
"string?" core-string?
|
"string?" core-string?
|
||||||
"number?" core-number?
|
"number?" core-number?
|
||||||
"fn?" core-fn?
|
"fn?" core-fn?
|
||||||
|
|
@ -2828,8 +2722,6 @@
|
||||||
"map?" core-map?
|
"map?" core-map?
|
||||||
"seq?" core-seq?
|
"seq?" core-seq?
|
||||||
"coll?" core-coll?
|
"coll?" core-coll?
|
||||||
"true?" core-true?
|
|
||||||
"false?" core-false?
|
|
||||||
"identical?" core-identical?
|
"identical?" core-identical?
|
||||||
"zero?" core-zero?
|
"zero?" core-zero?
|
||||||
"pos?" core-pos?
|
"pos?" core-pos?
|
||||||
|
|
@ -2856,8 +2748,6 @@
|
||||||
"mod" core-mod
|
"mod" core-mod
|
||||||
"rem" core-rem
|
"rem" core-rem
|
||||||
"quot" core-quot
|
"quot" core-quot
|
||||||
"max" core-max
|
|
||||||
"min" core-min
|
|
||||||
"rand" core-rand
|
"rand" core-rand
|
||||||
"rand-int" core-rand-int
|
"rand-int" core-rand-int
|
||||||
"=" core-=
|
"=" core-=
|
||||||
|
|
@ -2870,10 +2760,8 @@
|
||||||
"assoc" core-assoc
|
"assoc" core-assoc
|
||||||
"dissoc" core-dissoc
|
"dissoc" core-dissoc
|
||||||
"get" core-get
|
"get" core-get
|
||||||
"get-in" core-get-in
|
|
||||||
"contains?" core-contains?
|
"contains?" core-contains?
|
||||||
"count" core-count
|
"count" core-count
|
||||||
"trampoline" core-trampoline
|
|
||||||
"format" core-format
|
"format" core-format
|
||||||
"first" core-first
|
"first" core-first
|
||||||
"rest" core-rest
|
"rest" core-rest
|
||||||
|
|
@ -2887,20 +2775,14 @@
|
||||||
"__sqmap" core-sqmap
|
"__sqmap" core-sqmap
|
||||||
"__sqset" core-sqset
|
"__sqset" core-sqset
|
||||||
"into" core-into
|
"into" core-into
|
||||||
"merge" core-merge
|
|
||||||
"merge-with" core-merge-with
|
|
||||||
"keys" core-keys
|
"keys" core-keys
|
||||||
"vals" core-vals
|
"vals" core-vals
|
||||||
"select-keys" core-select-keys
|
|
||||||
"with-meta" core-with-meta
|
"with-meta" core-with-meta
|
||||||
"zipmap" core-zipmap
|
|
||||||
"map" core-map
|
"map" core-map
|
||||||
"filter" core-filter
|
"filter" core-filter
|
||||||
"remove" core-remove
|
"remove" core-remove
|
||||||
"reduce" core-reduce
|
"reduce" core-reduce
|
||||||
"apply" core-apply
|
"apply" core-apply
|
||||||
"key" core-key
|
|
||||||
"val" core-val
|
|
||||||
"map-entry?" core-map-entry?
|
"map-entry?" core-map-entry?
|
||||||
"special-symbol?" core-special-symbol?
|
"special-symbol?" core-special-symbol?
|
||||||
"promise" core-promise
|
"promise" core-promise
|
||||||
|
|
@ -2983,7 +2865,6 @@
|
||||||
"take-while" core-take-while
|
"take-while" core-take-while
|
||||||
"drop-while" core-drop-while
|
"drop-while" core-drop-while
|
||||||
"concat" core-concat
|
"concat" core-concat
|
||||||
"reverse" core-reverse
|
|
||||||
"nth" core-nth
|
"nth" core-nth
|
||||||
"sort" core-sort
|
"sort" core-sort
|
||||||
"sort-by" core-sort-by
|
"sort-by" core-sort-by
|
||||||
|
|
@ -2992,8 +2873,6 @@
|
||||||
"identity" core-identity
|
"identity" core-identity
|
||||||
"constantly" core-constantly
|
"constantly" core-constantly
|
||||||
"comp" core-comp
|
"comp" core-comp
|
||||||
"partial" core-partial
|
|
||||||
"memoize" core-memoize
|
|
||||||
"vector" core-vector
|
"vector" core-vector
|
||||||
"hash-map" core-hash-map
|
"hash-map" core-hash-map
|
||||||
"array-map" core-array-map
|
"array-map" core-array-map
|
||||||
|
|
|
||||||
|
|
@ -54,3 +54,51 @@
|
||||||
["munge dashes" "\"a_b\"" "(munge \"a-b\")"]
|
["munge dashes" "\"a_b\"" "(munge \"a-b\")"]
|
||||||
["munge symbol" "\"x_y\"" "(munge (quote x-y))"]
|
["munge symbol" "\"x_y\"" "(munge (quote x-y))"]
|
||||||
["test no-test" ":no-test" "(test (quote foo))"])
|
["test no-test" ":no-test" "(test (quote foo))"])
|
||||||
|
|
||||||
|
# Phase 2 leaf batch 2 (jolt-ded): canonical ports of key/val/select-keys/
|
||||||
|
# zipmap/merge/merge-with/get-in/memoize/partial/trampoline/some?/true?/false?/
|
||||||
|
# max/min/reverse, plus find (previously missing entirely).
|
||||||
|
(defspec "clojure.core / leaf batch 2"
|
||||||
|
["key" "1" "(key (first {1 :a}))"]
|
||||||
|
["val" ":a" "(val (first {1 :a}))"]
|
||||||
|
["key non-entry throws" :throws "(key 5)"]
|
||||||
|
["find hit" "[:a 1]" "(find {:a 1} :a)"]
|
||||||
|
["find miss" "nil" "(find {:a 1} :b)"]
|
||||||
|
["find nil value" "[:a nil]" "(find {:a nil} :a)"]
|
||||||
|
["find on vector" "[0 :x]" "(find [:x :y] 0)"]
|
||||||
|
["select-keys" "{:a 1}" "(select-keys {:a 1 :b 2} [:a])"]
|
||||||
|
["select-keys nil val" "{:a nil}" "(select-keys {:a nil :b 2} [:a])"]
|
||||||
|
["select-keys missing" "{}" "(select-keys {:a 1} [:z])"]
|
||||||
|
["zipmap" "{:a 1 :b 2}" "(zipmap [:a :b] [1 2])"]
|
||||||
|
["zipmap uneven" "{:a 1}" "(zipmap [:a :b] [1])"]
|
||||||
|
["zipmap nil val" "{:a nil}" "(zipmap [:a] [nil])"]
|
||||||
|
["merge" "{:a 1 :b 2}" "(merge {:a 1} {:b 2})"]
|
||||||
|
["merge later wins" "{:a 2}" "(merge {:a 1} {:a 2})"]
|
||||||
|
["merge nil arg" "{:a 1}" "(merge {:a 1} nil)"]
|
||||||
|
["merge nil first" "{:a 1}" "(merge nil {:a 1})"]
|
||||||
|
["merge all nil" "nil" "(merge nil nil)"]
|
||||||
|
["merge empty" "nil" "(merge)"]
|
||||||
|
["merge entry pair" "{:a 1 :b 2}" "(merge {:a 1} [:b 2])"]
|
||||||
|
["merge-with" "{:a 3}" "(merge-with + {:a 1} {:a 2})"]
|
||||||
|
["merge-with disjoint" "{:a 1 :b 2}" "(merge-with + {:a 1} {:b 2})"]
|
||||||
|
["merge-with nil-val present" "{:a 1}" "(merge-with (fn [a b] (or a b)) {:a nil} {:a 1})"]
|
||||||
|
["get-in" "1" "(get-in {:a {:b 1}} [:a :b])"]
|
||||||
|
["get-in missing" ":nf" "(get-in {:a 1} [:z :y] :nf)"]
|
||||||
|
["get-in nil value present" "nil" "(get-in {:a {:b nil}} [:a :b] :nf)"]
|
||||||
|
["get-in empty path" "{:a 1}" "(get-in {:a 1} [])"]
|
||||||
|
["memoize" "2" "(do (def c (atom 0)) (def f (memoize (fn [x] (swap! c inc) x))) (f 1) (f 1) (f 2) (deref c))"]
|
||||||
|
["memoize caches nil" "1" "(do (def c (atom 0)) (def f (memoize (fn [x] (swap! c inc) nil))) (f 1) (f 1) (deref c))"]
|
||||||
|
["partial" "6" "((partial + 1 2) 3)"]
|
||||||
|
["partial no extra" "3" "((partial + 1 2))"]
|
||||||
|
["partial many fixed" "15" "((partial + 1 2 3 4) 5)"]
|
||||||
|
["trampoline" "10" "(trampoline (fn f [n acc] (if (zero? n) acc (fn [] (f (dec n) (+ acc 2))))) 5 0)"]
|
||||||
|
["some? true" "true" "(some? 0)"]
|
||||||
|
["some? false" "false" "(some? nil)"]
|
||||||
|
["true?/false?" "[true false false]" "[(true? true) (true? 1) (false? nil)]"]
|
||||||
|
["max" "3" "(max 1 3 2)"]
|
||||||
|
["min" "1" "(min 3 1 2)"]
|
||||||
|
["max single" "5" "(max 5)"]
|
||||||
|
["max non-number throws" :throws "(max 1 :a)"]
|
||||||
|
["reverse" "(quote (3 2 1))" "(reverse [1 2 3])"]
|
||||||
|
["reverse empty" "()" "(reverse nil)"]
|
||||||
|
["conj nil onto map" "{:a 1}" "(conj {:a 1} nil)"])
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue