Merge pull request #49 from jolt-lang/leaf-shrink-batch3

core: Stage 3 — leaf batch 3: empty/assoc-in/update-in + interpose/take-nth
This commit is contained in:
Dmitri Sotnikov 2026-06-10 15:26:47 -04:00 committed by GitHub
commit 1ec2fa4adf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 107 additions and 64 deletions

View file

@ -732,3 +732,31 @@
([x y & more] (reduce min (min x y) more)))
(defn reverse [coll] (reduce conj (list) coll))
;; --- Phase 2 leaf batch 3 (jolt-ded) -----------------------------------------
;; An empty coll of the same category; sorted colls keep their comparator (the
;; value's own :empty op). Strings and scalars are nil, as in Clojure; a lazy
;; seq empties to () (the old kernel fn returned a host table for it).
(defn empty [coll]
(cond
(nil? coll) nil
(sorted? coll) ((get (jolt.host/ref-get coll :ops) :empty) coll)
(map? coll) {}
(set? coll) #{}
(vector? coll) []
(coll? coll) ()
:else nil))
(defn assoc-in [m [k & ks] v]
(if ks
(assoc m k (assoc-in (get m k) ks v))
(assoc m k v)))
(defn update-in [m ks f & args]
(let [up (fn up [m ks f args]
(let [[k & ks] ks]
(if ks
(assoc m k (up (get m k) ks f args))
(assoc m k (apply f (get m k) args)))))]
(up m ks f args)))

View file

@ -138,3 +138,40 @@
(when (seq s)
(cons (take n s) (go (nthrest s step))))))]
(go coll))))
;; --- Phase 2 leaf batch 3 (jolt-ded): canonical lazy + transducer arities ----
(defn interpose
([sep]
(fn [rf]
(let [started (volatile! false)]
(fn
([] (rf))
([result] (rf result))
([result input]
(if (deref started)
(let [sepr (rf result sep)]
(if (reduced? sepr)
sepr
(rf sepr input)))
(do (vreset! started true)
(rf result input))))))))
([sep coll]
(drop 1 (interleave (repeat sep) coll))))
(defn take-nth
([n]
(fn [rf]
(let [iv (volatile! -1)]
(fn
([] (rf))
([result] (rf result))
([result input]
(let [i (vswap! iv inc)]
(if (zero? (rem i n))
(rf result input)
result)))))))
([n coll]
(lazy-seq
(when-let [s (seq coll)]
(cons (first s) (take-nth n (drop n s)))))))

View file

@ -2251,19 +2251,10 @@
(defn- ks-rest [ks]
(if (tuple? ks) (tuple/slice ks 1) (array/slice ks 1)))
(defn core-assoc-in [m ks v]
(let [ks (vview ks) k (in ks 0)]
(if (<= (length ks) 1)
(core-assoc m k v)
(let [sub (core-get m k)]
(core-assoc m k (core-assoc-in (if (nil? sub) {} sub) (ks-rest ks) v))))))
# assoc-in / update-in now live in the Clojure collection tier (canonical
# recursive ports).
(defn core-update-in [m ks f & args]
(let [ks (vview ks) k (in ks 0)]
(if (<= (length ks) 1)
(core-assoc m k (apply f (core-get m k) args))
(let [sub (core-get m k)]
(core-assoc m k (apply core-update-in (if (nil? sub) {} sub) (ks-rest ks) f args))))))
# fnil now lives in the Clojure collection tier (core/20-coll.clj), with
# Clojure's canonical 2/3/4-arity (patch the first 1-3 args only).
@ -2329,61 +2320,21 @@
{:jolt/type :symbol :ns ns :name nm})
(error "symbol expects 1 or 2 args")))
(defn- td-take-nth [n]
(fn [rf]
(var i 0)
(fn [& a] (case (length a) 0 (rf) 1 (rf (a 0))
(let [keep (= 0 (mod i n))] (++ i)
(if keep (rf (a 0) (a 1)) (a 0)))))))
(defn core-take-nth [n & rest]
(if (= 0 (length rest)) (td-take-nth n)
(let [coll (in rest 0)]
# Option A: always lazy.
(defn tstep [c]
(fn []
(if (seq-done? c) nil
(let [drop-n (lazy-from (core-drop n c))]
(if (seq-done? drop-n) @[(core-first c) nil]
@[(core-first c) (tstep drop-n)])))))
(make-lazy-seq (tstep (lazy-from coll))))))
# (take-nth's transducer arity lives in the overlay now.)
# filterv now lives in the Clojure collection tier (core/20-coll.clj).
# mapv lives in the Clojure kernel tier — core/00-kernel.clj.
(defn- td-interpose [sep]
(fn [rf]
(var started false)
(fn [& a] (case (length a) 0 (rf) 1 (rf (a 0))
(if started (rf (rf (a 0) sep) (a 1))
(do (set started true) (rf (a 0) (a 1))))))))
(defn core-interpose [sep & rest]
(if (= 0 (length rest)) (td-interpose sep)
(let [coll (in rest 0)]
# Option A: always lazy.
(defn istep [c need-sep]
(fn []
(if (seq-done? c) nil
(if need-sep
@[sep (istep c false)]
@[(core-first c) (istep (core-rest c) true)]))))
(make-lazy-seq (istep (lazy-from coll) false)))))
# (interpose's transducer arity lives in the overlay now.)
# interpose / take-nth now live in the Clojure lazy tier (core/40-lazy.clj),
# with the canonical transducer arities.
# keep now lives in the Clojure lazy tier (core/40-lazy.clj).
(defn core-empty [coll]
(cond
# an empty sorted coll of the same kind, KEEPING the comparator (Clojure)
(core-sorted? coll) ((sorted-op coll :empty) coll)
(phm? coll) (make-phm)
(set? coll) (make-phs)
(plist? coll) EMPTY-PLIST
(pvec? coll) (make-vec @[])
(struct? coll) (struct)
(tuple? coll) (make-vec @[])
(array? coll) @[]
(table? coll) @{}
nil))
# empty now lives in the Clojure collection tier (core/20-coll.clj); a lazy
# seq empties to () there (this fn returned a host table for it).
# not-empty now lives in the Clojure collection tier (core/20-coll.clj).
@ -2840,7 +2791,6 @@
"newline" core-newline
"current-time-ms" core-current-time-ms
"parse-uuid" core-parse-uuid
"interpose" core-interpose
"mapcat" core-mapcat
"transduce" core-transduce
"sequence" core-sequence
@ -2851,8 +2801,6 @@
"namespace" core-namespace
"reduced" core-reduced
"reduced?" core-reduced?
"take-nth" core-take-nth
"empty" core-empty
"rseq" core-rseq
"shuffle" core-shuffle
"ifn?" core-ifn?
@ -3030,8 +2978,6 @@
"ThreadLocal" core-ThreadLocal
"IllegalStateException" core-IllegalStateException
"resolve" core-resolve
"update-in" core-update-in
"assoc-in" core-assoc-in
"copy-core-var" core-copy-core-var
"copy-var" core-copy-var
"macrofy" core-macrofy

View file

@ -102,3 +102,35 @@
["reverse" "(quote (3 2 1))" "(reverse [1 2 3])"]
["reverse empty" "()" "(reverse nil)"]
["conj nil onto map" "{:a 1}" "(conj {:a 1} nil)"])
# Phase 2 leaf batch 3 (jolt-ded): empty/assoc-in/update-in (20-coll) and
# interpose/take-nth (40-lazy, with canonical transducer arities). keys/vals/
# empty? are expander-coupled (00-syntax macros call them) and stay in the
# seed until the fast macro-expansion path lands.
(defspec "clojure.core / leaf batch 3"
["empty vector" "[]" "(empty [1 2])"]
["empty list" "()" "(empty (list 1))"]
["empty map" "{}" "(empty {:a 1})"]
["empty set" "#{}" "(empty #{1})"]
["empty nil" "nil" "(empty nil)"]
["empty string" "nil" "(empty \"abc\")"]
["empty lazy is ()" "()" "(empty (map inc [1 2]))"]
["empty sorted keeps cmp" "[3 1]" "(vec (seq (into (empty (sorted-set-by > 1 2)) [1 3])))"]
["assoc-in" "{:a {:b 1}}" "(assoc-in {} [:a :b] 1)"]
["assoc-in deep" "{:a {:b {:c 2}}}" "(assoc-in {:a {:b {:c 1}}} [:a :b :c] 2)"]
["assoc-in keeps siblings" "{:a {:b 1 :c 2}}" "(assoc-in {:a {:b 1}} [:a :c] 2)"]
["assoc-in vector idx" "[1 9]" "(assoc-in [1 2] [1] 9)"]
["assoc-in nested vec" "[{:a 9}]" "(assoc-in [{:a 1}] [0 :a] 9)"]
["update-in" "{:a {:b 2}}" "(update-in {:a {:b 1}} [:a :b] inc)"]
["update-in extra args" "{:a {:b 111}}" "(update-in {:a {:b 1}} [:a :b] + 10 100)"]
["update-in fnil" "{:a {:b 1}}" "(update-in {} [:a :b] (fnil inc 0))"]
["update-in single key" "{:a 2}" "(update-in {:a 1} [:a] inc)"]
["interpose" "(quote (1 :s 2 :s 3))" "(interpose :s [1 2 3])"]
["interpose empty" "()" "(interpose :s [])"]
["interpose one" "(quote (1))" "(interpose :s [1])"]
["interpose is lazy" "(quote (0 :s 1))" "(take 3 (interpose :s (range)))"]
["interpose xform" "[\"a\" \",\" \"b\"]" "(vec (sequence (interpose \",\") [\"a\" \"b\"]))"]
["take-nth" "(quote (1 3 5))" "(take-nth 2 [1 2 3 4 5 6])"]
["take-nth lazy" "(quote (0 3 6))" "(take 3 (take-nth 3 (range)))"]
["take-nth xform" "[1 3 5]" "(vec (sequence (take-nth 2) [1 2 3 4 5 6]))"]
["take-nth into" "[1 4]" "(into [] (take-nth 3) [1 2 3 4 5])"])