-
Notifications
You must be signed in to change notification settings - Fork 152
Fix method amguity in Scalar(::StaticArray) #774
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
Conversation
|
Not sure what went wrong in that CI job. Is it just network trouble? |
|
Tests pass locally. |
| @inline Scalar(x::Tuple{T}) where {T} = Scalar{T}(x[1]) | ||
| @inline Scalar(a::AbstractArray) = Scalar{typeof(a)}((a,)) | ||
| @inline Scalar(a::AbstractScalar) = Scalar{eltype(a)}((a[],)) # Do we want this to convert or wrap? | ||
| @inline Scalar(a::SA) where {SA<:StaticArray} = Scalar{SA}(a) # solve ambiguity |
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 Scalar(Scalar(0)) == Scalar(0) does not hold with this? As we already have Scalar(fill(0)) == Scalar(0) == Scalar((0,)), maybe it should?
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.
Hm, I hadn't considered that. It that does conflict a bit with the precendent already set, but I find that precedent kinda suspicious. It seems like it makes it very hard to make nested scalar objects. i.e. it makes things like this really hard:
julia> f(x, y) = x .+ y
f (generic function with 1 method)
julia> f.(Ref(Ref([1,2])), (([1,2], [3,4]), ([5,6],)))
(([2, 4], [4, 6]), ([6, 8],))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'd expect Scalar(Scalar(0)) == Scalar(0) since
julia> Array{<:Any,0}(fill(0))
0-dimensional Array{Int64,0}:
0The behavior of Array{<:Any,0} is expected given this recommendation in the manual:
convert(T, x) is expected to return the original x if x is already of type T. In contrast, if T is a mutable collection type then T(x) should always make a new collection (copying elements from x).
--- https://docs.julialang.org/en/v1.5-dev/manual/conversion-and-promotion/#Mutable-collections-1
Stretching this recommendation a bit, in general, given
@assert Union{T,S} <: Container
x::SI expect
isequal(T(x), x)if Container is "narrow enough" (it obviously won't work if Container = Any).
To use Scalar for Ref, I think we need a factory
scalar(x) = Scalar((x,))that ensures the wrapping behavior. This is compatible with how tuple and Tuple work.
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.
Yeah exactly - in Julia we end up tending to need factories like tuple, collect, etc to create collections rather than using the constructor (here we have a Tuple constructor to “bootstrap” but we could equally have created an explicit tuple->static array generic function, and otherwise we do attempt to follow the patterns in Base with the constructors).
(I note that generally we see the same need for extra functions with anything that “wraps” or “contains” one or more objects, like adjoint vs Adjoint.)
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 it's actually kind of (half?) opposite for adjoint vs Adjoint. The contractor Adjoint always wraps the object while the factory adjoint doesn't behave this way. But yeah, it seems that it's unavoidable to have two entry points.)
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.
This is the interesting part. The output of the generic interface adjoint does a better job of behaving as if it were always wrapped in another Adjoint than the constructor Ajdoint would using the copy semantics that seems common to AbstractArray constructors (your point above about Scalar(Scalar(x)) == Scalar(x) might as well be the same as Adjoint(Adjoint(x)) == Adjoint(x)). It's just that adjoint can get away with removing an Adjoint wrapper because of special properties of Adjoint - but IMO even without that the adjoint function would likely still be necessary.
|
I restarted CI in the hope that my CI workaround from #776 would apply, but maybe it needs a rebase first? |
|
It seems we didn't resolve what |
|
There was a short discussion with @mbauman on
(in response to my comment above #774 (comment) on the recommendation in Julia manual about the constructor) and
So... it looks like everyone doesn't like this complication but consistency wins...? |
So as a general rule I've been aiming for consistency with Base here rather than trailblaizing. I.e. the interface should mimic On Zulip @mbauman wrote:
Yeah I agree - I also seem to recall Matt working through constructor issues for 1.0 :).
|
|
I think it'd be bad if Also, I don't think we should be recommending this as a replacement to |
That's also a reasonable argument. Hmm... ?
You mean as our conceptual managed pointer? Of course not. Or do you mean the way |
Ah, yes, that's what I meant. |
|
xref #518 |
|
The original issue of this PR is already solved, probably by #1016. julia> using StaticArrays
julia> Scalar(SA[1,2,3])
Scalar{SVector{3, Int64}}(([1, 2, 3],))
julia> Scalar{SArray{Tuple{3},Int64,1,3}}(([1, 2, 3],))
Scalar{SVector{3, Int64}}(([1, 2, 3],)) |
Before this pr: