seq fns are lazy by default, like Clojure (LazySeq, not eager-headed)
map/filter/remove/take/drop/concat/take-while/drop-while/mapcat/partition built an eager-headed cseq: the first element (and the fn application) ran at construction, so a side-effecting (map f coll) fired f immediately and (class (map …)) was PersistentList instead of LazySeq. This diverged from Clojure, which wraps the whole body in lazy-seq. It went unnoticed because the conformance gate certifies values, not realization — eager and lazy heads produce identical values — and unit.edn even baked PersistentList in as expected. test.check's for-all-takes-multiple-expressions (which counts side effects in a for-all body) exposed it. Wrap each native producer's result in a lazy-seq node so the body, incl. the first element, defers until forced — the forced cseq still has eager heads, so reduce/count/dorun/etc. force on walk and there's no per-element cost. dedupe's (seq coll) is moved inside its lazy-seq. A jolt LazySeq is now recognized by coll?/empty, the analyzer's form predicates (a macro can build its expansion with map), value-host-tags + instance? (LazySeq/ISeq/ Sequential), and reports clojure.lang.LazySeq. Kept the native Scheme implementations rather than porting Clojure's: a straight lazy-seq+cons port is 3x slower and Clojure's chunked fast path is 288x slower because jolt's chunk machinery is unoptimized (filed jolt-j9dz); the wrapped natives are Clojure-lazy at native speed. +12 corpus rows (laziness at construction, LazySeq type, both JVM-certified). make test + shakesmoke green, selfhost holds, 0 new divergences.
This commit is contained in:
parent
92368b49f1
commit
b879430618
10 changed files with 80 additions and 40 deletions
|
|
@ -46,7 +46,9 @@
|
|||
;; ANY non-empty seq is a list form for analysis (a macro/eval form built via
|
||||
;; concat/map/cons is a lazy cseq with list?=#f, but evaluating it still means
|
||||
;; calling its head) — not just reader-built lists.
|
||||
(define (hc-list? x) (or (empty-list-t? x) (cseq? x)))
|
||||
;; a lazy seq is a list form too: a macro that builds its expansion with map/for
|
||||
;; (now a LazySeq, not an eager cseq) and splices it must still analyze.
|
||||
(define (hc-list? x) (or (empty-list-t? x) (cseq? x) (jolt-lazyseq? x)))
|
||||
(define (hc-vec? x) (pvec? x))
|
||||
(define (hc-map? x) (and (pmap? x) (jolt-nil? (jolt-get x hc-kw-jolt-type))))
|
||||
;; A set form is the reader's tagged map {:jolt/type :jolt/set :value <pvec>} OR a
|
||||
|
|
@ -106,7 +108,7 @@
|
|||
;; list items -> jolt vector (pvec); the analyzer mapv's over the result.
|
||||
(define (hc-elements x)
|
||||
(cond ((empty-list-t? x) empty-pvec)
|
||||
((cseq? x) (make-pvec (list->vector (seq->list x))))
|
||||
((or (cseq? x) (jolt-lazyseq? x)) (make-pvec (list->vector (seq->list x))))
|
||||
(else empty-pvec)))
|
||||
(define (hc-vec-items x) x) ; already a pvec
|
||||
(define (hc-set-items x)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue