Files
bara-lang/docs/PHASE5_HAMT.md
T
dimgigov d763e25638 Phase 5: HAMT Persistent Vector + CI fixes
- Add lib/cljnim_pvec.nim: 32-way HAMT Persistent Vector with structural sharing
- Migrate ckVector from seq[CljVal] to PersistentVector[CljVal] in runtime
- Fix recursive pushLeaf bug (nil nodes at depth > 2)
- Fix defn/if return value bug: replace discard with result = in proc bodies
- Fix cljNth to accept CljVal index
- Add len and [] overloads for PersistentVector seq compatibility
- Add tests/test_pvec.nim (14 tests)
- Update .gitlab-ci.yml to nim:2.2.10 and add test_pvec
- Update docs/ROADMAP.md and add PHASE5_HAMT.md
2026-05-08 17:58:00 +03:00

72 lines
2.9 KiB
Markdown

# Phase 5: HAMT Persistent Vector
## Status: COMPLETE
### What was implemented
**Hash Array Mapped Trie (HAMT) Persistent Vector** — 32-way branching trie with path-copying structural sharing, matching Clojure's `PersistentVector` semantics.
- `lib/cljnim_pvec.nim` — Core implementation
- `PersistentVector[T]` — root pointer + count + shift + tail
- `pvecConj` — O(log₃₂ N) append with structural sharing
- `pvecNth` — O(log₃₂ N) random access
- `pvecAssoc` — O(log₃₂ N) update with path copying
- `pvecPop` — O(log₃₂ N) remove last
- `newPersistentVector` — builder from `seq[T]`
- `toSeq` — convert back to `seq[T]`
- `len`, `[]` overloads for seq-like interface
**Integration into Runtime**
- `lib/cljnim_runtime.nim`: `ckVector` now stores `PersistentVector[CljVal]`
- All 51 `vecData` references migrated to use `pvecNth`/`toSeq`/`count`
- `cljVector` constructor builds from `seq[CljVal]` via `newPersistentVector`
### Architecture Decisions
1. **Tail optimization**: Like Clojure, maintains a 32-element tail buffer for fast append of recent items
2. **Lazy tree growth**: Root promotion only happens when tree is full (`needsNewRoot` check)
3. **Path copying**: Every `conj`/`assoc` copies only the nodes along the path to the changed leaf
4. **Seq compatibility**: Added `len` and `[]` overloads so `mapIt`, `join`, `items` iterator work transparently
### Bug fixes during implementation
- **Recursive `pushLeaf` bug**: When `child.isNil` and `shift != BRANCHING_BITS`, was creating empty internal nodes without recursing to place the leaf. Fixed by calling `pushLeaf(newInternalNode(), index, shift-5, val)` instead of just `newInternalNode()`.
### Tests
- `tests/test_pvec.nim` — 14 tests covering:
- Empty vector, single element, few elements
- `conj` one-by-one (100 elements)
- `conj` across tail boundary (32)
- `conj` across second level (1024)
- `conj` large (10000)
- `assoc` in tail and tree
- `assoc` preserves old vector (structural sharing)
- `pop` from tail, across boundary, to empty
### Performance Characteristics
| Operation | Complexity | Notes |
|-----------|-----------|-------|
| `conj` | O(log₃₂ N) | ~1-3 node copies |
| `nth` | O(log₃₂ N) | ~3-5 pointer hops |
| `assoc` | O(log₃₂ N) | ~1-3 node copies |
| `pop` | O(log₃₂ N) | May pull leaf into tail |
| `toSeq` | O(N) | Full traversal |
For N ≤ 1M, depth ≤ 3 (shift ≤ 15), making operations effectively constant-time.
### Migration Guide for Other AIs
If you need to work with vectors in `cljnim_runtime.nim`:
```nim
# Instead of:
v.vecData.len # Use: v.vecData.count
v.vecData[0] # Use: pvecNth(v.vecData, 0)
v.vecData[1..^1] # Use: toSeq(v.vecData)[1..^1]
for item in v.vecData: # Use: for item in v.vecData.items:
```
`mapIt` from `sequtils` works transparently thanks to `len` + `items` iterator overloads.