Conversation
Joining with a TinyRefNode swapped operands without inverting the identity mask, so an unchanged destination reported COUNTER_IDENT and join_into replaced it with the source. merge_guts and the integer pjoin also under-reported identities, giving Element for unchanged joins. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019R2H8fnco29asY2v3TPbtF
Meets and joins keep the left operand's value on a collision, but a byte node paired with a list node ran the operation with operands swapped and kept the list node's values. join_k_path_into also folded k-paths in reverse. Values now follow operand order and path order. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019R2H8fnco29asY2v3TPbtF
prune_path walked the node stack up to find where pruning stops but left it there when the zipper did not move, so the next write through the focus went to the wrong node (get_val_or_set_mut panicked). Walk the stack back down. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019R2H8fnco29asY2v3TPbtF
An empty link sharing its key with a value in the same list node carries nothing. Subtract dropped it and reported Element for an unchanged node; it is now Identity. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019R2H8fnco29asY2v3TPbtF
A path survives a meet exactly when both sides have it, dangling or not, and a value exactly when both hold one. Dense bytes that meet to nothing stay as dangling paths, list nodes meet slot by slot keeping the shared key prefix, and meet_into never removes its focus. With prune, dangling paths below the focus are dropped, possibly skipping nodes shared with the source. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019R2H8fnco29asY2v3TPbtF
meet keeps a location both sides have, dangling or not, and a value both hold. meetPruned drops dangling paths; since pathmap may skip shared nodes with prune, the fuzzer compares prune = false only. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019R2H8fnco29asY2v3TPbtF
|
What's the impact for performance here (both constant, and asymptotic)? The right call may be to change the spec to care less about dangling paths. |
|
The current implementation of |
//! `meet` of a map with itself keeps a dangling path; `meet` with an equal map
//! built separately drops it.
use pathmap::PathMap;
use pathmap::zipper::*;
fn locations(m: &PathMap<u64>) -> Vec<(Vec<u8>, Option<u64>)> {
let mut rz = m.read_zipper();
let mut out = vec![];
while rz.to_next_step() {
if rz.val().is_some() || rz.child_count() == 0 { out.push((rz.path().to_vec(), rz.val().cloned())); }
}
out
}
fn build() -> PathMap<u64> {
let mut map = PathMap::new();
map.set_val_at([1], 1);
map.create_path([0, 2, 1]);
map.set_val_at([1, 2, 2], 1);
map.write_zipper_at_path(&[0, 2, 1]).remove_branches(true);
map
}
fn main() {
let map = build();
let same = build();
println!("map {:?}", locations(&map));
println!("map.meet(&map) {:?}", locations(&map.meet(&map)));
println!("map.meet(&clone) {:?}", locations(&map.meet(&map.clone())));
println!("map.meet(&same) {:?}", locations(&map.meet(&same)));
println!("map == same paths: {}", locations(&map) == locations(&same));
assert_eq!(locations(&map.meet(&map)), locations(&map.meet(&same)), "meet depends on node identity");
} |
|
"the same" is load bearing here; "equivalent up to" with "dangling paths" and "sharing" and... |
|
Yikes... It took me more than 2 weeks to write the generic meet function back in early 2025. It was one of the trickiest parts of the whole crate - and the crate has a lot of tricky parts. I know Fable can probably do better than the un-assisted me of 18 months ago. Especially with the fuzzer to help. But hopefully we can get some more extensive benchmarks and test coverage on how well we preserve sharing too so we make sure not to lose any perf... Anyway, I don't want to rubber stamp this PR because it's huge and hits the heart of tricky stuff. But I don't have time to spend really understanding this week. |
As recently as 2025 I thought "load bearing" was clever engineer-architect-speak. Now it's a calling card of slop. |
There was an issue with meet: meet with self-node is a noop, but meet with equal node drops dangling paths.
This refines the meet spec to preserve dangling paths if they're both present.