Repository navigation
Non-assignment binary operations shouldn't mutate input #83
Description
Activity
I guess it's intentional, I didn't think too much about this choice.
Which do you expect:
- Unsupported operation?
- Returns an OwnedArray?
As it is, it's useful to some extent since it allows
a + b + c + dwhereais a mutable view.That would have to be replaced by
a += &b; a += c; a += &d;ora.iadd(&b); a.iadd(&c); a.iadd(&d);. The former with+=is ok, the iadds are a bit noisy..Unfortunately we will maybe not get rid of the
iaddeven when+=is stable, because+=is not applicable on rvalues, i.e. in this situation:a.slice_mut(s![..4, ..4]) += 1.0; // ERRORIsn't there the 3rd option of never doing the operation in place and instead cloning the array, doing the operations on the clone, and then returning it?
I don't necessarily expect
A @ Bto be in-place anyways and so performance is secondary.For rvalues, there are already some examples in the tests, which demand binding the view to an lvalue first. While needing one more line, I think doing the below is fine:
let mut asliced = a.slice_mut(s![..4, ..4]); asliced += 1.0;
But I have a feeling I misunderstood the issue.
That sounds like the second alternative, return OwnedArray.
Which way would have performance as primary objective? I think a + b must be good. Right now &a + &b creates a new array for the result and a + b reuses the allocation in a.
@vbarrielle What do you think?
I haven't played with the binary operations for the moment, I'll have a look.
I've been playing a bit with @SuperFluffy's example. I find the behaviour on mutable views surprising,it might be a pain point for users.
On the other hand I really like the current behaviour of requiringlet res = a.clone() + b.view()to get an OwnedArray, it makes the allocation explicit.I also feel that having
let x = a.view_mut() + bv;mutateacan be usefull in some cases, ie there are situations where you really don't wan't to allocate.I'm thinking there could be a way to keep this feature without having a surprising behaviour:
- don't implement binary operations on
ArrayViewMut, preventing that case from compiling - create a newtype over
ArrayViewMut, on which the binary operations are implemented. - let arrays create such an "in place operations view", enabling patterns such as
let _ = a.iop_mut() + b.view() + c.view() + d.view()
- don't implement binary operations on
There's a summary here actually https://bluss.github.io/rust-ndarray/master/ndarray/struct.ArrayBase.html#arithmetic-operations
I'm reading your thoughts, but I wanted to say that there are more combinations, for example that
&A + &Aalways creates a new OwnedArray without explicit clone/to_owned.We can easily take the step to remove the ArrayViewMut impls, just to test what happens.
Is an in place operations view necessary? The user still as
.iadd()and+=available.No it's not necessary, repeated calls to
iaddare fine imo.+=is not yet in stable rust is it?Simply removing the impl on ArrayViewMut would be a good move imho.
I'm reading your thoughts, but I wanted to say that there are more combinations, for example that &A + &A and &A + B always create a new OwnedArray without explicit clone/to_owned.
Ok I had not seen those. Well I think it still makes the allocation somehow explicit, ie there is no way the result could be written into A.
In summary: I like the current implementation of binary operations, except for the ArrayViewMut impl part.
Thank you for your thoughts! I think we will remove the ViewMut impls. I'll update the arithmetic ops overview, I think it's quite necessary to have a summary when there are so many
implclauses.There is very good news -- all in place operators are being stabilized for Rust 1.8.
It's nice that in place operators are getting stabilized! Can't wait to use them.
Maybe with impl specialization landing sometime in the future, the impls on
ViewMutcould then have a different behaviour?- added a commit that references this issue
on Feb 28, 2016 Not sure if specialization impacts this or not. Maybe it can be implemented with a different behavior already now.
However, the use of generics in this crate is already very complicated. We want to have some logical system that the users will understand — it seems easier to just leave this unimplemented in that case.
In the example at the bottom, addition of two arrays yields a new one, but also mutates one of the inputs. This is because the
add()function in theAddtrait is defined in terms of the in-placeiadd()(from the macroimpl_binary_opmacro):This is fine, if self is an
OwnedArrayor anArraybecause they are consumed anyways, but if instead anArrayViewMutis passed in, the underlying data changes. Is this intended? I think one shouldn't expect the array to get changed when addition is performed on a view.