-
Notifications
You must be signed in to change notification settings - Fork 149
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
Fix method amguity in Scalar(::StaticArray) #774
Conversation
Not sure what went wrong in that CI job. Is it just network trouble? |
Tests pass locally. |
@@ -9,6 +9,7 @@ const Scalar{T} = SArray{Tuple{},T,0,1} | |||
@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}:
0
The 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::S
I 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: