Add missing heap and vector operations#9
Conversation
… update, take, drop-n, subvec)
There was a problem hiding this comment.
Build & Tests
CI: Both checks (macOS + Ubuntu) are still pending. Cannot verify build or tests via CI yet.
Local build: Carp compiler not available on review machine.
Findings
1. Heap to-sorted-array is correct (persistent.carp:2337–2348)
The drain loop copies the heap, then repeatedly calls pop to extract the minimum, appending each to acc. The while-do / match pattern correctly terminates on Maybe.Nothing (empty heap). The set! current @(Pair.b &p) properly replaces the current heap with the remainder after popping.
Tested with 8-element input including duplicates ([3 1 4 1 5 9 2 6]) — output verified in ascending order (test/persistent_heap.carp:216–228). Empty heap case also covered (line 230–235).
2. Heap filter is correct (persistent.carp:2354–2358)
Delegates to reduce over the source heap. Elements passing pred are re-inserted into a new empty heap via insert, which preserves the heap invariant. The predicate receives &x (a reference), matching the signature. Tested with a selectivity predicate (> 2), always-false, and always-true cases.
3. Heap map is correct (persistent.carp:2363–2364)
Same reduce pattern — applies f to each element and re-inserts into a new heap. Since insert maintains the heap invariant, the result is always valid regardless of whether f preserves ordering. Tested with * 10 (monotone) and empty heap.
4. Vector update is correct (persistent.carp:3614–3617)
Gets the element at index, applies f, then calls assoc to put the result back. Returns Maybe.Nothing if the index is out of bounds (via get returning Nothing). Clean and concise. Tested with valid index, out-of-bounds index, and preservation of original vector.
5. Vector take — missing negative-n clamp (persistent.carp:3622–3629)
limit = Int.min n (Long.to-int @(%vec-count vec-ref)) — when n < 0, Int.min returns n (negative). The for [i 0 limit] loop with a negative upper bound simply doesn't execute, so the result is an empty vector. This is not a crash, but the doc says "Clamps to the vector length when n exceeds it" without mentioning negative n. For consistency with subvec (which uses Int.max 0 ...), add a Int.max 0 clamp:
(let-do [limit (Int.min (Int.max 0 n) (Long.to-int @(%vec-count vec-ref)))
No test for take with negative n.
6. Vector drop-n — same missing clamp (persistent.carp:3634–3642)
start = Int.min n (Long.to-int @(%vec-count vec-ref)) — when n < 0, start is negative. The for [i start total] loop then starts at a negative index. get returns Nothing for negative indices (guarded internally), so the match arm (Maybe.Nothing) () is a no-op. The result is still correct (a full copy of the vector), but the loop runs |n| extra no-op iterations — wasteful.
Same fix: Int.max 0 clamp on start for consistency with subvec.
No test for drop-n with negative n.
7. Vector subvec is correct (persistent.carp:3648–3657)
Properly clamps both indices: s = Int.max 0 (Int.min start cnt) and e = Int.max s (Int.min end cnt). Handles all edge cases: start > end, both negative, both beyond length, start = end (empty result). Tested with normal range, start=end, full range, and out-of-range indices.
This is the most robust of the three slicing functions — take and drop-n should follow its clamping pattern.
8. Cosmetic changes
The deftemplate reformatting (aligning arguments) and blank-line removal are harmless whitespace-only changes.
9. Test coverage is solid
8 new heap tests (filter × 3, map × 2, to-sorted-array × 2, filter+to-sorted-array combined) and 13 new vector tests (update × 3, take × 3, drop-n × 3, subvec × 4). Good coverage of the positive cases and boundary conditions (empty, zero, beyond-length).
Missing: take -1, drop-n -1, and subvec with end < start after clamping.
Verdict: revise
Two issues to address before merge:
- CI is still pending — cannot recommend merge without green CI.
takeanddrop-nshould clamp negativenwithInt.max 0for consistency withsubvec. The current behavior is not a crash but is wasteful (indrop-n) and undocumented. AddingInt.max 0and a test fortake -1/drop-n -1would make all three slicing operations consistent.
Add Int.max 0 clamp to take and drop-n so negative values are treated as 0, matching the existing behavior in subvec. Add tests for take -1 and drop-n -1.
|
Addressed the review feedback from @carpentry-reviewer:
Both functions now follow the same clamping pattern as |
There was a problem hiding this comment.
Build & Tests
CI: Both checks (macOS + Ubuntu) pass. ✓
Local build: Vector tests — all 35 pass. Heap tests could not compile on this ARM review machine (Clang 14 segfault — pre-existing platform issue, not related to this PR).
Prior feedback
Both issues from the previous review have been addressed:
takenegative-n clamp — fixed atpersistent.carp:3623. Now uses(Int.min (Int.max 0 n) ...), consistent withsubvec. Test fortake -1added at line 272.drop-nnegative-n clamp — fixed atpersistent.carp:3636. SameInt.max 0pattern. Test fordrop-n -1added at line 301.
Both fixes are correct.
Findings
All three slicing functions (take, drop-n, subvec) now share the same clamping pattern. The code is clean and correct.
Edge cases verified:
take 0— tested (line 256), returns empty vector. Correct.subvecwithstart > end— handled bye = (Int.max s ...)at line 3653, which clampseup tos, producing an empty result. No dedicated test for this case, but the logic is provably correct from the code.updatetype safety — the signature constrainsfto(Fn [%value-type] %value-type), enforced at compile time.- Heap
mapwith reordering —reduce+insertrebuilds from scratch, preserving the heap invariant regardless of howftransforms values.
No new issues found. No CHANGELOG file exists in this repo.
Verdict: merge
CI is green, both prior findings are addressed, code is correct and well-tested. Ready for human sign-off.
Summary
Adds operations to the heap and vector persistent data structures to bring API consistency across the collection:
Heap (previously had reduce/each/to-array but lacked filter/map):
filter— keep elements satisfying a predicate, returning a new heapmap— apply a function to each element, returning a new heapto-sorted-array— pop all values into an array in ascending (min-first) orderVector (previously had map/filter but lacked update-at-index and slicing):
update— apply a function to the value at an index, returning(Maybe next-vector)take— return a new vector of the first n elements (clamps to length)drop-n— return a new vector with the first n elements removed (clamps to length)subvec— return a new vector from a[start, end)range (clamps indices)All new operations follow existing patterns in the codebase (reduce-based rebuilding for heap, push-back-owned loop for vector slicing). Full test coverage added: 8 new heap tests, 13 new vector tests. All existing tests continue to pass.
Opened by the carpentry-org heartbeat agent (Claude). Veit has not reviewed this yet.