-
Notifications
You must be signed in to change notification settings - Fork 9
RFC: Blind zippers #25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
…ypes to the "Blind" zipper API Also filling in missing impls in PolyZipper b/c the default impls are sooooo much less efficient. They're mainly there to model correct behavior
|
A few questions / comments. Please don't take my perspective as authority; It's likely there is a good reason the code is the way it is that I'm just not seeing.
|
Yes, it's possible to make it simpler. The point is to make the API harder to mis-use.
Yeah, I think changing it to the number of steps ascended is a good idea. I don't even remember why I chose this way, and I did consider returning the "number of steps ascended". Regardless of other changes, I think returning the number of bytes ascended is the right choice here.
Yes, this would make a more slim API. the reason I used Here's my understanding of tradeoffs:
Overall, because of consistency, simplicity and better optimizations, using
The purpose of this struct is to wrap a zipper and add
|
|
> Option > Remaining vs ascended > Result > Path naming |
In general, I totally agree with this philosophy. But in this particular case, I think “failed to ascend” and “ascended 0 bytes” are actually exactly the same thing. Not just in effect but also in concept. The only way you could ascend zero bytes when calling one of these functions is if ascend failed, and the failure conditions are clearly defined.
Haha. That’s the worst. That same set of concerns leads me to try not to cast bools to numbers in rust, and thankfully going the other way isn't allowed (unlike C!)
I’ll narrow it down to PathWrapper and PathTracker. Not sure which I prefer. Maybe |
|
Oh shit.... I just did this work... |
|
Lemme merge and see if I missed anything you got, or vice versa |
|
So far, our changes are line-for-line identical excepting things like variable names and doc updates. A good sign. |
Unfortunate. I just saw the comment and implemented the changes as a test. |
…ppers. Making all `ascend_` variants always return the number of bytes ascended UPDATE: Merging with Igor's nearly identical work
|
I've never had so many conflicts resolve so easily. We literally wrote exactly the same code (structurally) in 90% of cases. |
Agh! We did it again. Both implemented the same changes at the same time
|
> Option
On the usability side, descend_until should really return how many bytes are descended, and not require to take the difference between the old and new vector length. Having potential allocation inlined is also expensive, so the code should be specialized based on the now runtime Option. Moreover, demanding a Vec eliminates the possibility of using a fixed-sized buffer here. This can be solved by a |
I would argue that this is not a problem, since the function returning I agree there's some weirdness around what does it mean to have a blind zipper. Perhaps we should discuss this, and it's possible that this change is not needed if there's no consistency.
|
|
> For a C-style, generics-free API, *mut u8 works, and the returned usize written bytes can be used to process the result (with a null pointer obviously not being written to). Implemented in 78bb6b5 but overlooked an important factor: descend_until needs to be bounded (either internally or externally) or something in the vein of "to allocate or not to allocate: that is the question". This seems to favor a |
I commented exactly this point here, a couple days ago: Line 315 in 193b48e
Although my intuition was that this argued for moving the lateral (sibling) movement into another trait. But after the discussion last night I'm feel pragmatically that adding |
I almost agree with this definition. To me a "blind zipper" is the idea that a zipper doesn't need to track its path internally to implement I don't think the phrase "blind zipper" is going to mean much to users (or even us) beyond just this PR. It's just a handy shorthand for the change we want to make. However I think this PR is useful and good. I.e. it is conceptually consistent and it provides a valuable practical benefit. |
I am not sure I understand the objection to returning From a usability standpoint, just call From a perf standpoint, |
…s opposed to `Option<&mut Vec<u8>>` and fixing a couple other bugs & todos along the way
|
Just pushed a change to make the Line 249 in 18e2080
|
|
> since the function returning None indicates that 1) the zipper didn't move 2) there's no next sibling That's precisely the problem: it doesn't provide anything extra, while acting like it does! If you map 2) to 0u8 or last_byte_unchecked(), now you have more information. If you remove 1) (at root) and just call at_root(), now you have more information. Hell with last_byte(), you can just return a boolean again, and the API usage just stays clean and no information is lost! |
… impls of sibling movement methods
… but now is part of the new `ZipperPath` trait
As previously discussed, this moves towards Zipper API to allow "blind" zippers, where the user can track the path.
Changes:
ZipperPath: New trait to return path as a single continuous slice.fn pathmoved out ofZipperMovingtoZipperPathfn move_to_pathmoved out ofZipperMovingtoZipperPath. it really requires having an existing path.ZipperMoving:descend_indexed_byte,descend_indexed_branch,descend_first_byte,to_next_sibling_byte,to_prev_sibling_bytenow returnOption<u8>indicating which byte we just descended to.descend_untilnow accepts a bufferOption<&mut Vec<u8>>to push the newly descended path. Could have beenOption<&mut impl Write>, but&mut Vecis just simpler.ascendnow returns the number of remaining steps as aResult<(), usize>. Maybe not the prettiest API, but forces the user to handle ascending above root. Maybe it's a good idea to separate it into a different function, and keep the originalascendthe same.ascend_untilandascend_until_branchnow returnsOption<usize>, the number of bytes ascended.to_next_stepto allow the caller to track path might require more thought, so it is left with the default implementation.