-
-
Notifications
You must be signed in to change notification settings - Fork 11
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
Add alteration methods to IArray #34
base: main
Are you sure you want to change the base?
Conversation
I imagine this is being blocked by #35. |
I did not. The |
I was not talking about HAMT, I was talking about the rest... |
Yes actually I'm not sure this API proposed in this PR here is a good idea. Maybe the "to_vec()" is more than enough and more rust-y. It discussed more about it on #17 with my reasoning. I'm not strongly against either so I will go with the flow.
It might be worth adding an optional dependency on im/im-rc that will allow the user of implicit-clone to get the trait ImplicitClone on im/im-rc's types? This idea is really out of scope with this PR though. |
I didn't even notice all of the methods added by this PR, only paying attention to those in the PR's description, oops... I personally am not against To me, the non-resizable |
haahh you're shocked for nothing :P really, what do you think happens in other programing languages anyway? And yes the rational was that each element of the IArray must implement ImplicitClone so they are supposed to be cheap to clone. Now it's not like I particularly mind about this API at all. It's not very rust-y but it allows writing fp-style code easily wanna join 2 collections?
That's what the implicit cloning of IArray is actually trying to bring. Since the elements are cheap-to-clone, we are allowed to write things almost like a dynamic language. But again I don't have a strong opinion on this, it was a 0.x version. (IMap also has this kind of API in mind btw brining dynamic programming to rust, truly going against the norm and see what does it do. Maybe it is OP, maybe it's terrible lol, maybe both) |
023b0a9
to
4f60dc0
Compare
Includes IArray::make_mut, which works similar to Rc::make_mut, as well as insertion and removal methods, all of which copy-on-write the array.
I don't agree. The Rust-ism is that EDIT: In addition, you do not want to be frivolously cloning
EDIT2: You also can't borrow from the iterator if it clones: let arr: Vec<Rc<ReallyBigType>> = ...
// can't do this with IArray without cloning every node.string
let field = arr.iter().find(|node| &node.string).collect::<String>(); |
pub fn insert_many<I: IntoIterator<Item = T>>(&mut self, index: usize, values: I) { | ||
let head = self.as_slice()[..index].iter().cloned(); | ||
let tail = self.as_slice()[index..].iter().cloned(); | ||
let rc = head.chain(values).chain(tail).collect(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess our main complaint in accordance with #17 is this .collect()
here allocates a whole new array.
When iterating over items, sure, they can be cloned, since each item iteration makes a clone (ImplicitClone
that is, meaning not even allocating anything besides on the stack, just doing things like incrementing an Rc
count, for example) and then throws that clone out once its done.
Allocating a new array (also, WASM (since its a yew-stack crate) performance for such allocations I'm not sure about) means looking for a lot of space (both of the arrays combined could be massive - literally, in my native language word "array" translates to "massive"), and then moving all of the made clones into it. This means adding a new single item would require making a whole array clone of a worst-case size. Imagine adding two in a row?
Sure, the user could make some kind of temporary storage to insert all of new items at once, but the API cannot prove that user would not call push
for adding single items in a big loop instead. Colloquially in IT and other areas of engineering this is called making the design "idiot-proof". Vec
does actually amortize this kind of cost with separating size
and capacity
. Most of optimizations we could work on (talked over in #17) would probably lead us to just an inferior Vec
/Rc<Vec>
.
I believe its better to make the user do an intentional to_vec
(I wonder if its possible to optimize into a into_vec(self)
like make_mut
so it just copies bytes (moves?) into newly allocated Vec
in case there is only one Rc
?) or newly added make_mut
for just mutating some items, or get_mut
(I already commented that, I'll be waiting for that to be added / explained why it could not be added).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only way Rc<[T]> -> Vec<T>
could be more efficient than moving out of the Rc<[T]>
buffer would be if Vec<T>
reused the storage of Rc<[T]>
, but it can't - the Rc buffer would be prefixed by the refcount, which Vec won't know about and won't deallocate correctly. You can't even move values out of an Rc<[T]>
because try_unwrap
and into_inner
need [T]: Sized
. So copying is really the only way.
to_vec
-> edit -> collect::<Rc<[T]>>
would involve two copies, which isn't great. Using iterators over the original slice would be better but gets complex.
IMO the simple insert/remove methods are convenient for the usual UI things of adding/removing a single element, though I agree they are kinda footguns for more involved usage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only way
Rc<[T]> -> Vec<T>
could be more efficient than moving out of theRc<[T]>
buffer would be ifVec<T>
reused the storage ofRc<[T]>
The thought is that we don't necessarily clone
each item into the new Vec
, but move them instead. In most cases, a move is just like a copy, if not precisely that (but doing it manually would require unsafe
and working with Pin
I guess (also, do we need to handle Pin<IArray/IString/etc>
somehow?)).
You can't even move values out of an
Rc<[T]>
becausetry_unwrap
andinto_inner
need[T]: Sized
. So copying is really the only way.
I guess this is our limiting factor. Probably making a tracking issue on this repo that would ask to add to Rust into_vec
or something of the sort to the Rc<[...]>
, unless using unsafe
in this repo is a non-issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even with unsafe, you'd have to somehow be able to free the underlying Rc
allocation without dropping the items, and unlike with Vec, there's no guarantees on the allocation that Rc makes, since there's a refcount on it as well. Rust would need to add something like Rc::drain
.
EDIT: now that I think about it, you may be able to do it by transmuting to Rc<[MaybeUninit<T>]>
, moving the values out, then dropping the transmuted Rc, which will free the memory without running destructors. Assuming that's the only reference, of course.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't mind that this go in a separate PR so we can merge this one already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we're going to need this in Yew for the ChildrenRenderer
Post code comparisons here? Hopefully you are not trying to add individual items in a loop? |
I was going to make the PR now ^_^ |
Wait no, not for this. The mutable API would be useful to use IArray in VList instead of Vec. Though I'm not entirely sure how efficient this is... maybe it's terrible |
Includes IArray::make_mut, which works similar to Rc::make_mut, as well as insertion and removal methods, all of which copy-on-write the array.