-
-
Notifications
You must be signed in to change notification settings - Fork 9
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
document that 2-arg mul! methods trashes all arguments #338
Comments
I know I've mentioned it before, but I would like to plug the |
See also JuliaLang/julia#16702 |
Adding to this list the 2-argument |
I don't think |
There are a few methods where that's not the case:
If you write code where some inputs may need to have varying dimensionality of scalar, 1, or 2, there's an ambiguity here. |
Arguably those shouldn't exist. I had no idea what they were going to do until I looked at the code. I think that should be explicitly written as
(or rather whatever mutating equivalent we have). |
|
What does function foo(a, b)
scale!(a, b)
return a
end do then? If we can't give a consistent definition for what the generic function verb |
I think we should use types in the definitions. That was the point I tried to make in the last comment. |
I don't have a particularly strong opinion on the topic, but it feels like a pun to me. |
That defeats the purpose of generic programming. I think the non-commutative case would be better served by a edit: or |
I am also in favor of only having the 3-argument |
Unlikely to receive attention prior to 0.6. Best! |
Bump, if any linalg people are looking for things to do. |
Seems like the two-argument |
A majority of triage'ers think this behavior is fine if it's clearly documented, warning people that you can't predict which argument will be mutated. |
The three-argument |
In practice in current package code the two-argument versions are very rarely used, and when they are they are virtually always used for their side effects rather than assigning their output and using that. Given that usage, these are more harmful than helpful since their side effects aren't predictable in a generic way. |
A lot of the 3-argument versions will fail or give incorrect results if you reuse input and output arguments:
|
Looks like the BigInt method is skipping the aliasing checks that the Float64 method caught, that should be fixed. 2-arg methods wouldn't be able to work in any situations where 3-arg with the equivalent aliased input/output arguments doesn't. |
The three-argument methods being broken is a separate issue (which should be fixed, of course). I think the fact that 2-arg |
At some point, I tried implementing a subset of Lapack in pure Julia, and for those case where in place matrix multiplication and division are possible and useful (e.g. triangular matrices), I just still had a three argument version |
I found this more intuitive, i.e. it is explicitly clear which argument is overwritten. Like the implementation of generic matrix multiplication has to check against aliasing, in that case the implementation would look like function mul!(C::Matrix,A::Triangular,B::Matrix)
C !== B && copy!(C,B)
# ... in place multiplication of A onto C
end |
You specify it via the function: |
Ah, I thought the 2 was just for 2-argument version :-). At this point, it's probably personal preference. I think the fear for misuse is purely hyptothetical, especially if a more elaborate aliasing checking mechanism would be put in place. But even without, similar to how accidental aliassing does not seem to appear often or cause many problems currently. Personally, I find |
Precise checking is probably not possible, but checking if the underlying "buffer" array is the same is possible and we can fall back to a slow, alias-safe implementation in those cases. Of course, we'd also want tooling to help detect such situations and avoid it when it's a performance problem. |
Nitpick: the |
|
This really shouldn't be a doc issue, "overwrites one argument but which one depends on input types" isn't a useful thing for this function to do. |
Ok, we can reopen it if you want but I don't think it belongs on the 1.0 milestone since the function is not exported. |
Some aspects of this are still not totally clear to me. I can imagine two reasons for an API like the current
These are very different styles of use. The cases @andreasnoack is referring to seem to be (1). Are there any examples of (2)? If we mostly care about (1), then I do think it would be better to write such calls as |
When I last looked at the uses of 2-arg in place multiplication across packages over the summer, hardly any used and referred to the return value, which is the only sane way you might use (2) to do something worthwhile |
@JeffBezanson , |
I agree with @simonbyrne's criticism of this proposal in #338. You don't win much in terms of generic programming when I still don't get why it is so important to change the name or signature of methods that no users have reported issues with. The methods multiply matrices in-place so the name |
I think that's exactly the point --- if you hit a case where the types don't work, you get a method error instead of silently mutating a different value. |
Isn't it the other way around? At the moment you get a MethodError, if we change it to |
Also, I should point out that the two separate |
Not the way I'm thinking about it. Currently I can write |
I've updated my previous PR at JuliaLang/julia#25421. I did have a quick stab at trying the 3-element version, but it was far from clear how to go about it. |
Thanks for the nice work. For the alternative proposal of the 3-argument version, what is wrong with replacing function f(C,A,B)
C === A || copy!(C,A)
... and similar for Sure, this is a problem if |
It's not so straightforward: for some it should throw an error, for others that wouldn't actually work at all (e.g. some of the triangular types return a value of a different type that reuses the underlying array). If we still wanted to do it, we could implement that on top of JuliaLang/julia#25421. |
I trust your assessment as I am not too familiar with all the special array types. However, I do find that statement confusing, as above it was mentioned that almost all use-cases of the 2-argument version were not using the return type explicitly, but where just called as |
sounded familiar, so I skimmed base/linalg/triangular.jl to refresh my memory but found no examples. I encountered only the following somewhat idiosyncratic (perhaps accidental?) definition. Could you perhaps link a relevant definition or two for consideration? Thanks! :) |
Most are in triangular.jl, e.g. https://github.com/JuliaLang/julia/pull/25421/files#diff-f3a0525264ed2a88c32e4ec3f72c8fa4R163 |
Ah, |
Example methods are here for Triangular, or here for Diagonal where these follow the BLAS
trmm
convention of mutating whichever argument is not the type in question. SoA_mul_B!(A, B)
might mutate A, or it might mutate B, depending on their types. This is the case for several other combinations of input types as well, and I think it's bad for generic programming. Ref collapsed discussion right below JuliaLang/julia#16615 (comment), and JuliaLang/julia#16577 and JuliaLang/julia#16562.If we can't apply a consistent rule here, "always mutates the second argument" (or, if we apply it consistently everywhere, the first?), then we shouldn't define this method. Better to favor the more explicit
A_mul_B!(C, A, B)
method where the output is a separate argument. Some combinations of types can allow one ofA_mul_B!(A, A, B)
orA_mul_B!(B, A, B)
to work, but writing code that does that will be inherently non-generic. The individual methods ofA_mul_B!
should be responsible for checking whether the inputs are===
(and possibly more complicated forms of alias checks if we have more view types in common use) and only allowing the specific combinations that are expected to work.This issue is still present for all the in-place multiplication methods even if JuliaLang/julia#6837 renames all 7 (or 9 if you count possible combinations we don't define right now, ref #57) variants to methods of
mul!
. I think this is an explicitly post-0.5 cleanup to make though since we don't have time to deal with it right now.The text was updated successfully, but these errors were encountered: