conformance: audit + pin seq semantics (laziness, eagerness, chunking, type)

A 62-case jolt-vs-JVM probe across seq type identity, chunking
granularity, eagerness, and realization timing. Findings: the whole
producer family is lazy at construction (no eager bugs remain), and the
26 divergences fall into two classes that diverge by representation, not
value.

Lock in the laziness contract as certified corpus rows: construction=0
for keep/keep-indexed/map-indexed/distinct/partition-by/partition-all/
interpose/interleave/take-nth/reductions/tree-seq/replace, sequence
realizes 1, next realizes 2, rest realizes 1.

Pin the two accepted divergence classes (allowlisted, gate-guarded):
- seq-type-model: jolt reifies seqs as PersistentList/LazySeq vs JVM's
  Cons/Iterate/LongRange/Repeat/Cycle/ChunkedSeq/StringSeq/KeySeq/RSeq/
  ArraySeq/SubVector (jolt-aei7)
- chunking-model: unchunked, realizes one where JVM realizes a 32-chunk;
  mapcat/dedupe fully lazy at construction (jolt-mm6v)

known-divergences.edn gains both categories; SPEC.md documents the seq
semantics contract. Data/doc only, no re-mint. certify 0 new / 0 stale.
This commit is contained in:
Yogthos 2026-06-28 03:22:47 -04:00
parent 6d441e2d00
commit 59cfa5f53f
3 changed files with 94 additions and 2 deletions

View file

@ -84,6 +84,41 @@ implements. Current profile (≈2735 portable, ≈167 non-portable):
| `:impl/representation` | representation detail (e.g. syntax-quote yields a `list?`, not a `Cons`) |
| `:bug` | a *known defect* (tracked bead) — not a host difference |
## Seq semantics
Values alone don't pin laziness — an eager `map` and a lazy `map` return the same
elements. The spec certifies seq *semantics* by reducing them to values with a
side-effect counter, so the corpus catches a laziness regression the value
comparison would miss.
**Laziness (certified — jolt matches JVM).** The whole producer family
(`map`/`filter`/`remove`/`take`/`drop`/`concat`/`take-while`/`drop-while`/`mapcat`/
`partition`/`partition-all`/`partition-by`/`keep`/`keep-indexed`/`map-indexed`/
`distinct`/`interpose`/`interleave`/`take-nth`/`reductions`/`tree-seq`/`replace`)
is lazy at construction: building over a side-effecting source realizes **zero**
elements (`lazy / family is lazy at construction`). Realization order is
left-to-right, `take`/`nth`/`drop` realize exactly as far as demanded, a lazy seq
memoizes (realize-once across walks), and `next` realizes head + one lookahead
while `rest` realizes only the head (`lazy / realization order & count`,
`lazy / realization is memoized`, `lazy / realization timing`). A lazy result is
`clojure.lang.LazySeq`.
**Accepted divergences.** jolt is a simpler, finer-grained superset of JVM seq
behavior; two classes diverge by representation, never by value, and are
allowlisted in `known-divergences.edn`:
- **`:seq-type-model`** (`seq-type-model / …` suite, jolt-aei7) — jolt reifies
every seq as `PersistentList` (eager) or `LazySeq` (deferred). JVM has a
specialized class per producer (`Cons`, `Iterate`, `LongRange`, `Repeat`,
`Cycle`, `PersistentVector$ChunkedSeq`, `StringSeq`, `KeySeq`/`ValSeq`, `RSeq`,
`ArraySeq`, `SubVector`), so `(class …)` differs. `instance?
clojure.lang.ISeq/Sequential` and all values/laziness are correct.
- **`:chunking-model`** (`chunking-model / …` suite, jolt-mm6v) — jolt seqs are
unchunked: forcing one element realizes one, where JVM realizes a ~32-element
chunk; `mapcat`/`dedupe` realize 0 at construction where JVM forces the first
chunk. Strictly finer-grained laziness, decided after the chunk fast path
(jolt-j9dz) was made O(n).
## Hosting jolt on a new runtime
1. Implement the reader + analyzer + a backend for your runtime (see the Chez port