Shim java.util.Iterator: (.iterator coll)/.hasNext/.next over jolt collections (#103)

Some Clojure libraries loop with the Java Iterator protocol — e.g. hiccup's
iterate! does (let [it (.iterator coll)] (while (.hasNext it) (f (.next it)))).
jolt had no Iterator, so (.iterator coll) returned nil and the loop did nothing
(silently dropping content). Add an (.iterator coll) object-method that returns a
:jolt/iterator over any seqable, with hasNext/next; the collection is materialized
via core's realize-for-iteration (late-bound through the evaluator since core
loads after it, wired in api). Found while bringing up weavejester/hiccup.

Co-authored-by: Yogthos <yogthos@gmail.com>
This commit is contained in:
Dmitri Sotnikov 2026-06-15 00:28:45 +00:00 committed by GitHub
parent 874b6140ee
commit 288b20956c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 24 additions and 1 deletions

View file

@ -14,6 +14,11 @@
(import ./host_iface :as host)
(import ./javatime) # java.time shims register into the evaluator at load
# Wire core's collection realizer into the evaluator's (.iterator coll) shim —
# late-bound here because the evaluator loads before core. Makes Java-Iterator-
# style loops (e.g. hiccup's iterate!) work over any jolt collection.
(set-coll-realizer! realize-for-iteration)
# A defmacro expander compiles to a native fn (built as (fn args body...) and run
# through the self-hosted pipeline) so macro expansion is COMPILED code, zero runtime
# cost — instead of an interpreted closure, mirroring Clojure (macros are ordinary

View file

@ -629,6 +629,20 @@
# ctor fns are interned as clojure.core vars at init (install-stateful-fns!).
(def class-ctors @{})
(defn register-class-ctor! [nm f] (put class-ctors nm f))
# java.util.Iterator shim: (.iterator coll) gives a jolt iterator over any
# seqable, with (.hasNext it) / (.next it). Some Clojure libs (e.g. hiccup's
# iterate!) loop with the Java Iterator protocol; this makes that work over jolt
# collections. The realizer (core/realize-for-iteration, which handles every
# collection type) is late-bound because core loads after this file.
(var coll-realizer nil)
(defn set-coll-realizer! [f] (set coll-realizer f))
(register-tagged-methods! :jolt/iterator
@{"hasNext" (fn [self] (< (self :pos) (length (self :items))))
"next" (fn [self]
(def x (in (self :items) (self :pos)))
(put self :pos (+ 1 (self :pos)))
x)})
# Class names evaluate to their CANONICAL NAME STRING — the same value
# core-class returns — so (defmethod m String ...) keys match a
# (defmulti m (comp class :body)) dispatch (ring.util.request does this).
@ -694,7 +708,11 @@
"getCause" (fn [e] (and (table? e) (get e :cause)))
"toString" (fn [x] (string x))
"equals" (fn [a b] (deep= a b))
"hashCode" (fn [x] (hash x))})
"hashCode" (fn [x] (hash x))
# (.iterator coll) -> a jolt iterator (see :jolt/iterator above). Materializes
# the collection to an indexable array via the late-bound core realizer.
"iterator" (fn [coll] @{:jolt/type :jolt/iterator :pos 0
:items (if coll-realizer (coll-realizer coll) @[])})})
(def- string-methods
{"getBytes" (fn [s &opt charset] (buffer s))