Merge pull request #209 from jolt-lang/fix/delay-exn-deftype-method-merge

core.cache: full conformance (delay/deftype/dispatch fixes + Thread/CountDownLatch/SoftReference)
This commit is contained in:
Dmitri Sotnikov 2026-06-25 15:19:27 +00:00 committed by GitHub
commit e955b2072a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 716 additions and 581 deletions

View file

@ -45,6 +45,10 @@ the [examples](https://github.com/jolt-lang/examples), e.g. the
* [clojure.spec.alpha](https://github.com/clojure/spec.alpha) — data specs * [clojure.spec.alpha](https://github.com/clojure/spec.alpha) — data specs
* [core.match](https://github.com/clojure/core.match) — pattern matching. * [core.match](https://github.com/clojure/core.match) — pattern matching.
`JOLT_FEATURES` `clj`. `JOLT_FEATURES` `clj`.
* [core.cache](https://github.com/clojure/core.cache) — caching (Basic/FIFO/LRU/
LU/TTL/Soft + the wrapped atom API), over
[data.priority-map](https://github.com/clojure/data.priority-map).
`JOLT_FEATURES` `clj`.
* [tick](https://github.com/juxt/tick) — date/time over Jolt's `java.time`; * [tick](https://github.com/juxt/tick) — date/time over Jolt's `java.time`;
`#time/…` literals via `time-literals`. `JOLT_FEATURES` `clj`. `#time/…` literals via `time-literals`. `JOLT_FEATURES` `clj`.
* [transit-jolt](https://github.com/jolt-lang/transit-jolt) — Transit (JSON) read/write * [transit-jolt](https://github.com/jolt-lang/transit-jolt) — Transit (JSON) read/write

View file

@ -228,15 +228,21 @@
;; (delay body) -> (make-delay (fn [] body)) (overlay macro); force/deref run the ;; (delay body) -> (make-delay (fn [] body)) (overlay macro); force/deref run the
;; thunk once under a lock and cache the value (JVM delays are thread-safe). force ;; thunk once under a lock and cache the value (JVM delays are thread-safe). force
;; (overlay) is (if (delay? x) (deref x) x), so it works once delay?/deref do. ;; (overlay) is (if (delay? x) (deref x) x), so it works once delay?/deref do.
(define-record-type jolt-delay (fields thunk (mutable realized?) (mutable value) mu) (define-record-type jolt-delay (fields thunk (mutable realized?) (mutable value) (mutable exn) mu)
(nongenerative jolt-delay-v1)) (nongenerative jolt-delay-v1))
(define (jolt-make-delay thunk) (make-jolt-delay thunk #f jolt-nil (make-mutex))) (define (jolt-make-delay thunk) (make-jolt-delay thunk #f jolt-nil #f (make-mutex)))
;; run the thunk once, like Clojure's Delay: if it throws, cache the exception
;; (the delay IS realized) and re-throw it on every deref — do NOT re-run the
;; body (so value-fns memoize and there is no cache-stampede / retried side
;; effect). Store the exception inside the lock, re-raise outside it so the mutex
;; is always released.
(define (jolt-delay-force d) (define (jolt-delay-force d)
(with-mutex (jolt-delay-mu d) (with-mutex (jolt-delay-mu d)
(unless (jolt-delay-realized? d) (unless (jolt-delay-realized? d)
(jolt-delay-value-set! d (jolt-invoke (jolt-delay-thunk d))) (guard (e (#t (jolt-delay-exn-set! d e) (jolt-delay-realized?-set! d #t)))
(jolt-delay-realized?-set! d #t))) (jolt-delay-value-set! d (jolt-invoke (jolt-delay-thunk d)))
(jolt-delay-value d)) (jolt-delay-realized?-set! d #t))))
(if (jolt-delay-exn d) (raise (jolt-delay-exn d)) (jolt-delay-value d)))
;; --- deref extension -------------------------------------------------------- ;; --- deref extension --------------------------------------------------------
;; Chain the fully-built jolt-deref (atoms/vars/volatiles/reduced) with futures, ;; Chain the fully-built jolt-deref (atoms/vars/volatiles/reduced) with futures,
@ -322,3 +328,47 @@
(def-var! "jolt.host" "interrupt!" jolt-interrupt!) (def-var! "jolt.host" "interrupt!" jolt-interrupt!)
(def-var! "jolt.host" "interrupted?" jolt-interrupted?) (def-var! "jolt.host" "interrupted?" jolt-interrupted?)
(def-var! "jolt.host" "run-interruptible" jolt-run-interruptible) (def-var! "jolt.host" "run-interruptible" jolt-run-interruptible)
;; --- java.lang.Thread / java.util.concurrent.CountDownLatch -----------------
;; Real OS threads over Chez fork-thread (shared heap — a captured atom/var is
;; shared). A Thread runs its Runnable thunk; start forks, join waits on a
;; condition latched at completion. CountDownLatch is a counting barrier.
(define (make-jthread thunk) (make-jhost "user-thread" (vector thunk #f (make-mutex) (make-condition))))
(for-each (lambda (nm) (register-class-ctor! nm (lambda (thunk . _) (make-jthread thunk))))
'("Thread" "java.lang.Thread"))
(register-host-methods! "user-thread"
(list (cons "start" (lambda (self)
(let ((st (jhost-state self)) (snap (dyn-binding-stack)))
(fork-thread (lambda ()
(dyn-binding-stack snap)
(guard (e (#t #f)) (jolt-invoke (vector-ref st 0)))
(with-mutex (vector-ref st 2)
(vector-set! st 1 #t)
(condition-broadcast (vector-ref st 3)))))
jolt-nil)))
(cons "run" (lambda (self) (jolt-invoke (vector-ref (jhost-state self) 0)) jolt-nil))
(cons "join" (lambda (self . _)
(let ((st (jhost-state self)))
(with-mutex (vector-ref st 2)
(let loop () (unless (vector-ref st 1) (condition-wait (vector-ref st 3) (vector-ref st 2)) (loop)))))
jolt-nil))
(cons "isAlive" (lambda (self) (not (vector-ref (jhost-state self) 1))))
(cons "interrupt" (lambda (self . _) jolt-nil))
(cons "setDaemon" (lambda (self . _) jolt-nil))))
(define (make-jlatch n) (make-jhost "count-down-latch" (vector n (make-mutex) (make-condition))))
(for-each (lambda (nm) (register-class-ctor! nm (lambda (n . _) (make-jlatch (jnum->exact n)))))
'("CountDownLatch" "java.util.concurrent.CountDownLatch"))
(register-host-methods! "count-down-latch"
(list (cons "countDown" (lambda (self)
(let ((st (jhost-state self)))
(with-mutex (vector-ref st 1)
(when (> (vector-ref st 0) 0) (vector-set! st 0 (- (vector-ref st 0) 1)))
(when (= (vector-ref st 0) 0) (condition-broadcast (vector-ref st 2)))))
jolt-nil))
(cons "await" (lambda (self . _)
(let ((st (jhost-state self)))
(with-mutex (vector-ref st 1)
(let loop () (when (> (vector-ref st 0) 0) (condition-wait (vector-ref st 2) (vector-ref st 1)) (loop)))))
jolt-nil))
(cons "getCount" (lambda (self) (vector-ref (jhost-state self) 0)))))

View file

@ -211,6 +211,56 @@
(vector->list (hashtable-keys (hm-tbl self))))))) (vector->list (hashtable-keys (hm-tbl self)))))))
(cons "entrySet" (lambda (self) (jolt-seq (hm->pmap self)))) (cons "entrySet" (lambda (self) (jolt-seq (hm->pmap self))))
(cons "toString" (lambda (self) (jolt-pr-str (hm->pmap self)))))) (cons "toString" (lambda (self) (jolt-pr-str (hm->pmap self))))))
;; java.util.concurrent.ConcurrentHashMap — one shared heap, so the mutable
;; HashMap shim serves. (get a-hashmap k) reads the map (clojure.core/get).
(define (make-hashmap-jhost . args)
(let ((ht (make-hashtable hm-hash jolt=2)))
(when (and (pair? args) (or (pmap? (car args)) (hm-hashmap? (car args)))) (hm-copy-into! ht (car args)))
(make-jhost "hashmap" (vector ht))))
(register-class-ctor! "ConcurrentHashMap" make-hashmap-jhost)
(register-class-ctor! "java.util.concurrent.ConcurrentHashMap" make-hashmap-jhost)
(register-get-arm! (lambda (x) (and (jhost? x) (string=? (jhost-tag x) "hashmap")))
(lambda (coll k d) (hashtable-ref (hm-tbl coll) k d)))
;; count / contains? over the mutable map shim (clojure.core/count + contains?,
;; which core.cache's SoftCache uses on its backing ConcurrentHashMap).
(define (jhost-hashmap? x) (and (jhost? x) (string=? (jhost-tag x) "hashmap")))
(let ((prev-count jolt-count) (prev-contains jolt-contains?))
(set! jolt-count (lambda (c) (if (jhost-hashmap? c) (hashtable-size (hm-tbl c)) (prev-count c))))
(set! jolt-contains? (lambda (c k) (if (jhost-hashmap? c)
(if (hashtable-contains? (hm-tbl c) k) #t #f)
(prev-contains c k)))))
;; ---- java.lang.ref.SoftReference / ReferenceQueue ---------------------------
;; No JVM GC reference semantics here: a SoftReference holds its referent strongly
;; (never auto-cleared), which makes a SoftCache an unbounded cache rather than a
;; GC-pressure one. .enqueue / ReferenceQueue.poll work so code that drains the
;; queue (clojure.core.cache's clear-soft-cache!) runs. soft-ref state:
;; #(referent queue enqueued?); ref-queue state: #(list-of-enqueued-refs).
(define (refqueue-add! rq ref)
(let ((st (jhost-state rq))) (vector-set! st 0 (append (vector-ref st 0) (list ref)))))
(for-each (lambda (nm)
(register-class-ctor! nm
(lambda (v . rest) (make-jhost "soft-ref" (vector v (if (pair? rest) (car rest) jolt-nil) #f)))))
'("SoftReference" "java.lang.ref.SoftReference"))
(register-host-methods! "soft-ref"
(list (cons "get" (lambda (self) (vector-ref (jhost-state self) 0)))
(cons "clear" (lambda (self) (vector-set! (jhost-state self) 0 jolt-nil) jolt-nil))
(cons "isEnqueued" (lambda (self) (vector-ref (jhost-state self) 2)))
(cons "enqueue" (lambda (self)
(let* ((st (jhost-state self)) (rq (vector-ref st 1)))
(if (vector-ref st 2) #f
(begin (vector-set! st 2 #t)
(when (and (jhost? rq) (string=? (jhost-tag rq) "ref-queue")) (refqueue-add! rq self))
#t)))))))
(for-each (lambda (nm) (register-class-ctor! nm (lambda _ (make-jhost "ref-queue" (vector '())))))
'("ReferenceQueue" "java.lang.ref.ReferenceQueue"))
(register-host-methods! "ref-queue"
(list (cons "poll" (lambda (self . _)
(let* ((st (jhost-state self)) (q (vector-ref st 0)))
(if (null? q) jolt-nil (begin (vector-set! st 0 (cdr q)) (car q))))))
(cons "remove" (lambda (self . _)
(let* ((st (jhost-state self)) (q (vector-ref st 0)))
(if (null? q) jolt-nil (begin (vector-set! st 0 (cdr q)) (car q))))))))
;; ---- StringReader ----------------------------------------------------------- ;; ---- StringReader -----------------------------------------------------------
;; state: a vector #(string pos marked). ;; state: a vector #(string pos marked).

View file

@ -19,7 +19,9 @@
(define (queue-peek q) (if (null? (jolt-queue-front q)) jolt-nil (car (jolt-queue-front q)))) (define (queue-peek q) (if (null? (jolt-queue-front q)) jolt-nil (car (jolt-queue-front q))))
(define (queue-pop q) (define (queue-pop q)
(let ((f (jolt-queue-front q))) (let ((f (jolt-queue-front q)))
(cond ((null? f) (error 'pop "can't pop empty queue")) ;; popping an empty PersistentQueue returns it (Clojure's pop: if f==null
;; return this) — unlike a vector, which throws.
(cond ((null? f) q)
((null? (cdr f)) (make-jolt-queue (reverse (jolt-queue-rear q)) '() (fx- (jolt-queue-cnt q) 1))) ((null? (cdr f)) (make-jolt-queue (reverse (jolt-queue-rear q)) '() (fx- (jolt-queue-cnt q) 1)))
(else (make-jolt-queue (cdr f) (jolt-queue-rear q) (fx- (jolt-queue-cnt q) 1)))))) (else (make-jolt-queue (cdr f) (jolt-queue-rear q) (fx- (jolt-queue-cnt q) 1))))))

View file

@ -62,8 +62,16 @@
"}")) "}"))
;; ---- extend the collection dispatchers with a jrec arm ---------------------- ;; ---- extend the collection dispatchers with a jrec arm ----------------------
;; equality for a jrec: a deftype implementing IPersistentCollection/equiv (e.g.
;; core.cache's caches, which equiv to their backing map) compares through that
;; method, so (= cache {…}) works; a plain record has no equiv and falls back to
;; field-wise jrec=? (and a record is never = a plain map).
(register-eq-arm! (lambda (a b) (or (jrec? a) (jrec? b))) (register-eq-arm! (lambda (a b) (or (jrec? a) (jrec? b)))
(lambda (a b) (and (jrec? a) (jrec? b) (jrec=? a b)))) (lambda (a b)
(cond ((and (jrec? a) (jrec-cl a "equiv")) => (lambda (m) (if (jolt-truthy? (jolt-invoke m a b)) #t #f)))
((and (jrec? b) (jrec-cl b "equiv")) => (lambda (m) (if (jolt-truthy? (jolt-invoke m b a)) #t #f)))
((and (jrec? a) (jrec? b)) (jrec=? a b))
(else #f))))
(register-hash-arm! jrec? jrec-hash) (register-hash-arm! jrec? jrec-hash)
;; get on a jrec: a real field reads raw (so a deftype method's own field bindings, ;; get on a jrec: a real field reads raw (so a deftype method's own field bindings,
;; compiled to (get inst :field), never recurse); a NON-field key on a deftype that ;; compiled to (get inst :field), never recurse); a NON-field key on a deftype that
@ -131,6 +139,10 @@
(else (%r-jolt-conj1 coll x))))) (else (%r-jolt-conj1 coll x)))))
;; peek/pop on a deftype implementing IPersistentStack (data.priority-map, which ;; peek/pop on a deftype implementing IPersistentStack (data.priority-map, which
;; core.cache's LRU/LU caches lean on) dispatch to its methods. ;; core.cache's LRU/LU caches lean on) dispatch to its methods.
;; empty? over a jrec: a map-like deftype is empty iff its entry seq is (data
;; .priority-map's peek calls (.isEmpty this) -> empty?). jolt-seq is method-first.
(define %r-jolt-empty? jolt-empty?)
(set! jolt-empty? (lambda (coll) (if (jrec? coll) (jolt-nil? (jolt-seq coll)) (%r-jolt-empty? coll))))
(define %r-jolt-peek jolt-peek) (define %r-jolt-peek jolt-peek)
(set! jolt-peek (lambda (coll) (set! jolt-peek (lambda (coll)
(cond ((jrec-cl coll "peek") => (lambda (m) (jolt-invoke m coll))) (cond ((jrec-cl coll "peek") => (lambda (m) (jolt-invoke m coll)))
@ -357,6 +369,10 @@
;; holding a mutable cursor over (seq coll); (.hasNext it)/(.next it) walk it. ;; holding a mutable cursor over (seq coll); (.hasNext it)/(.next it) walk it.
;; hiccup/compiler's run! loop iterates collections this way. ;; hiccup/compiler's run! loop iterates collections this way.
(define-record-type jiterator (fields (mutable cur)) (nongenerative jolt-iterator-v1)) (define-record-type jiterator (fields (mutable cur)) (nongenerative jolt-iterator-v1))
;; (seq an-iterator) / (iterator-seq it): a jiterator wraps the remaining seq in
;; cur, so seq just yields it — clojure.test's (iterator-seq (.iterator coll)).
(let ((prev-seq jolt-seq))
(set! jolt-seq (lambda (x) (if (jiterator? x) (jiterator-cur x) (prev-seq x)))))
;; A Chez condition's message string (for Throwable .getMessage/.toString): the ;; A Chez condition's message string (for Throwable .getMessage/.toString): the
;; &message text plus any &irritants, or display-condition output as a fallback. ;; &message text plus any &irritants, or display-condition output as a fallback.
(define (condition->message-string c) (define (condition->message-string c)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -323,35 +323,39 @@
;; inline impls register for dispatch but are NOT extenders of the ;; inline impls register for dispatch but are NOT extenders of the
;; protocol (the JVM compiles them into the class) — register-inline-method, ;; protocol (the JVM compiles them into the class) — register-inline-method,
;; not extend-type. ;; not extend-type.
;; Several bodies for one method name are distinct arities (a type that ;; build one method clause (argv + field-bound body) from a method spec.
;; implements clojure.lang.Indexed has (nth [_ i]) AND (nth [_ i x])): ;; The clause is DATA, not a syntax-quote: a body that is itself a syntax-
;; group them into one multi-arity fn so dispatch picks the clause by arg ;; quote would have its ~unquotes consumed a level early if re-spliced.
;; count, instead of the last clause clobbering the rest. mk-clause (fn [spec]
impl (fn [proto specs] (let [argv (nth spec 1)
(loop [ss (seq specs) methods {} order []] inst (first argv)
(if (empty? ss) binds (vec (mapcat (fn [f] [f `(get ~inst ~(keyword (name f)))]) fields))
`(do (register-inline-protocol! ~(name tname) ~(name proto)) mbody (map (fn [bf] (rewrite-set inst bf)) (drop 2 spec))]
~@(map (fn [mname] (list argv (list* 'let binds mbody))))
`(register-inline-method ~(name tname) ~(name proto) ~mname groups (group-by-head body)
(fn ~@(get methods mname)))) ;; merge clauses by method NAME across ALL protocols into one multi-arity
order)) ;; fn, so a name appearing in two interfaces with different arities
(let [spec (first ss) ;; (data.priority-map's seq is in Seqable [this] AND Sorted [this asc])
mname (name (first spec)) ;; dispatches by arg count instead of one registration shadowing the other.
argv (nth spec 1) ;; (Within one protocol, distinct arities like Indexed's nth merge the same
inst (first argv) ;; way.) Each (protocol, name) registers the merged fn, so dispatch by name
binds (vec (mapcat (fn [f] [f `(get ~inst ~(keyword (name f)))]) fields)) ;; and satisfies? by protocol both hold.
mbody (map (fn [bf] (rewrite-set inst bf)) (drop 2 spec)) by-name (reduce (fn [m spec]
;; build the clause as DATA, not via a syntax-quote: a body (let [nm (name (first spec))]
;; that is itself a syntax-quote (`(= ~ocr ~l)) would have its (assoc m nm (conj (get m nm []) (mk-clause spec)))))
;; ~unquotes consumed a level early if re-spliced through one. {} (mapcat rest groups))]
clause (list argv (list* 'let binds mbody))]
(recur (rest ss)
(assoc methods mname (conj (get methods mname []) clause))
(if (contains? methods mname) order (conj order mname)))))))]
`(do `(do
(def ~tname (make-deftype-ctor (quote ~tname) [~@field-kws] [~@field-tags] [~@field-muts])) (def ~tname (make-deftype-ctor (quote ~tname) [~@field-kws] [~@field-tags] [~@field-muts]))
(def ~arrow ~tname) (def ~arrow ~tname)
~@(map (fn [g] (impl (first g) (rest g))) (group-by-head body)) ~@(mapcat (fn [g]
(let [proto (first g)
names (distinct (map (fn [spec] (name (first spec))) (rest g)))]
(cons `(register-inline-protocol! ~(name tname) ~(name proto))
(map (fn [nm]
`(register-inline-method ~(name tname) ~(name proto) ~nm
(fn ~@(get by-name nm))))
names))))
groups)
~tname))) ~tname)))
;; The protocol value is built by make-protocol (a fn call) rather than an embedded ;; The protocol value is built by make-protocol (a fn call) rather than an embedded
@ -491,35 +495,35 @@
;; inline impls register for dispatch but are NOT extenders of the ;; inline impls register for dispatch but are NOT extenders of the
;; protocol (the JVM compiles them into the class) — register-inline-method, ;; protocol (the JVM compiles them into the class) — register-inline-method,
;; not extend-type. ;; not extend-type.
;; group multi-arity method bodies into one fn (see deftype's impl). ;; one clause from a spec; `this` is hinted with the record type so the
impl (fn [proto specs] ;; inference reads its fields bare-index. Clause as DATA (see deftype).
(loop [ss (seq specs) methods {} order []] mk-clause (fn [spec]
(if (empty? ss) (let [argv (nth spec 1)
`(do (register-inline-protocol! ~(name name-sym) ~(name proto)) inst (first argv)
~@(map (fn [mname] hinted (assoc argv 0 (vary-meta inst assoc :tag (name name-sym)))
`(register-inline-method ~(name name-sym) ~(name proto) ~mname binds (vec (mapcat (fn [f] [f `(get ~inst ~(keyword (name f)))]) fields))]
(fn ~@(get methods mname)))) (list hinted (list* 'let binds (drop 2 spec)))))
order)) groups (group-by-head body)
(let [spec (first ss) ;; merge clauses by name across protocols into one multi-arity fn (see
mname (name (first spec)) ;; deftype's by-name).
argv (nth spec 1) by-name (reduce (fn [m spec]
inst (first argv) (let [nm (name (first spec))]
;; hint `this` with the record type so the inference types it (assoc m nm (conj (get m nm []) (mk-clause spec)))))
;; and its field reads bare-index instead of the runtime guard. {} (mapcat rest groups))]
hinted (assoc argv 0 (vary-meta inst assoc :tag (name name-sym)))
binds (vec (mapcat (fn [f] [f `(get ~inst ~(keyword (name f)))]) fields))
;; clause as DATA (see deftype): a syntax-quote body must not
;; be re-spliced through another syntax-quote.
clause (list hinted (list* 'let binds (drop 2 spec)))]
(recur (rest ss)
(assoc methods mname (conj (get methods mname []) clause))
(if (contains? methods mname) order (conj order mname)))))))]
`(do `(do
;; deftype already defines ->name (= the ctor); no (name. …) interop needed, ;; deftype already defines ->name (= the ctor); no (name. …) interop needed,
;; so defrecord compiles too. map->name builds via that ctor. ;; so defrecord compiles too. map->name builds via that ctor.
(deftype ~name-sym ~fields) (deftype ~name-sym ~fields)
(def ~mapf (fn* [~m] (~arrow ~@(map (fn [f] `(get ~m ~(keyword (name f)))) fields)))) (def ~mapf (fn* [~m] (~arrow ~@(map (fn [f] `(get ~m ~(keyword (name f)))) fields))))
~@(map (fn [g] (impl (first g) (rest g))) (group-by-head body))))) ~@(mapcat (fn [g]
(let [proto (first g)
names (distinct (map (fn [spec] (name (first spec))) (rest g)))]
(cons `(register-inline-protocol! ~(name name-sym) ~(name proto))
(map (fn [nm]
`(register-inline-method ~(name name-sym) ~(name proto) ~nm
(fn ~@(get by-name nm))))
names))))
groups))))
;; --- laziness -------------------------------------------------------------- ;; --- laziness --------------------------------------------------------------
;; lazy-seq / lazy-cat moved to the 00-syntax tier: the seq/coll tiers (10-seq, ;; lazy-seq / lazy-cat moved to the 00-syntax tier: the seq/coll tiers (10-seq,

View file

@ -6,6 +6,15 @@
{:suite "fn / pre-post" :label ":pre + :post pass" :expected "5" :actual "(do (defn ppf [x] {:pre [(pos? x)] :post [(= % x)]} x) (ppf 5))"} {:suite "fn / pre-post" :label ":pre + :post pass" :expected "5" :actual "(do (defn ppf [x] {:pre [(pos? x)] :post [(= % x)]} x) (ppf 5))"}
{:suite "fn / pre-post" :label ":pre failure throws" :expected ":blocked" :actual "(do (defn ppg [x] {:pre [(pos? x)]} x) (try (ppg -1) (catch Throwable _ :blocked)))"} {:suite "fn / pre-post" :label ":pre failure throws" :expected ":blocked" :actual "(do (defn ppg [x] {:pre [(pos? x)]} x) (try (ppg -1) (catch Throwable _ :blocked)))"}
{:suite "deftype / map-like dispatch" :label "dissoc + keys via IPersistentMap/Seqable methods" :expected "[:b]" :actual "(do (deftype MapT [m] clojure.lang.Seqable (seq [_] (seq m)) clojure.lang.IPersistentMap (without [_ k] (->MapT (dissoc m k))) (assoc [_ k v] (->MapT (assoc m k v)))) (vec (keys (dissoc (->MapT {:a 1 :b 2}) :a))))"} {:suite "deftype / map-like dispatch" :label "dissoc + keys via IPersistentMap/Seqable methods" :expected "[:b]" :actual "(do (deftype MapT [m] clojure.lang.Seqable (seq [_] (seq m)) clojure.lang.IPersistentMap (without [_ k] (->MapT (dissoc m k))) (assoc [_ k v] (->MapT (assoc m k v)))) (vec (keys (dissoc (->MapT {:a 1 :b 2}) :a))))"}
{:suite "delay / exception memoization" :label "throwing body runs once, stays realized, re-throws" :expected "[1 true]" :actual "(let [n (atom 0) d (delay (swap! n inc) (throw (ex-info \"e\" {})))] (dotimes [_ 3] (try (deref d) (catch Throwable _ nil))) [(deref n) (realized? d)])"}
{:suite "queue / pop" :label "pop of empty PersistentQueue returns empty" :expected "nil" :actual "(seq (pop clojure.lang.PersistentQueue/EMPTY))"}
{:suite "interop / iterator-seq" :label "iterator-seq over .iterator" :expected "[:a :b]" :actual "(vec (iterator-seq (.iterator [:a :b])))"}
{:suite "deftype / IPersistentStack" :label "peek/pop dispatch" :expected "[1 [2 3]]" :actual "(do (deftype Stk [v] clojure.lang.IPersistentStack (peek [_] (first v)) (pop [_] (->Stk (rest v)))) [(peek (->Stk [1 2 3])) (vec (.v (pop (->Stk [1 2 3]))))])"}
{:suite "deftype / equiv" :label "(= deftype other) uses equiv method" :expected "true" :actual "(do (deftype EqT [m] clojure.lang.IPersistentCollection (equiv [_ o] (= o m)) clojure.lang.Seqable (seq [_] (seq m))) (= (->EqT {:a 1}) {:a 1}))"}
{:suite "interop / Thread" :label "start + join runs the thunk" :expected "7" :actual "(let [a (atom 0) t (Thread. (fn [] (reset! a 7)))] (.start t) (.join t) (deref a))"}
{:suite "interop / CountDownLatch" :label "countDown to zero, await returns" :expected "0" :actual "(let [l (java.util.concurrent.CountDownLatch. 1)] (.countDown l) (.await l) (.getCount l))"}
{:suite "interop / SoftReference" :label "get returns the referent" :expected ":v" :actual "(.get (java.lang.ref.SoftReference. :v))"}
{:suite "interop / ConcurrentHashMap" :label "put + get/count/contains?" :expected "[1 1 true]" :actual "(let [m (java.util.concurrent.ConcurrentHashMap.)] (.put m :a 1) [(get m :a) (count m) (contains? m :a)])"}
{:suite "interop / Class.forName" :label "unknown class throws ClassNotFoundException" :expected ":nf" :actual "(try (Class/forName \"no.such.Klass\") (catch ClassNotFoundException _ :nf))"} {:suite "interop / Class.forName" :label "unknown class throws ClassNotFoundException" :expected ":nf" :actual "(try (Class/forName \"no.such.Klass\") (catch ClassNotFoundException _ :nf))"}
{:suite "interop / Class.forName" :label "known class resolves" :expected "\"java.lang.String\"" :actual "(.getName (Class/forName \"java.lang.String\"))"} {:suite "interop / Class.forName" :label "known class resolves" :expected "\"java.lang.String\"" :actual "(.getName (Class/forName \"java.lang.String\"))"}
{:suite "clojure.string / replace" :label "char match and replacement" :expected "\"a-b-c\"" :actual "(clojure.string/replace \"a/b/c\" \\/ \\-)"} {:suite "clojure.string / replace" :label "char match and replacement" :expected "\"a-b-c\"" :actual "(clojure.string/replace \"a/b/c\" \\/ \\-)"}