From c1620f0595b02bab67e055294850b19dffafa549 Mon Sep 17 00:00:00 2001 From: Chris Foster Date: Sat, 18 Dec 2021 12:09:34 +1000 Subject: [PATCH] Named element access with .{x,y,z,w} for SVector and MVector (#980) Having access with v.x etc makes the *concrete* SVector interface much more usable for geometry; indeed, it will "just work like you expect" without the need to resort to a custom FieldVector type. This makes the concrete SVector/MVector interface fatter, but I don't think it can do any harm to people who don't want to use it. --- docs/src/index.md | 21 --------------------- docs/src/pages/quickstart.md | 17 +++++++++++++++++ src/MArray.jl | 4 ++-- src/MVector.jl | 21 +++++++++++++++++++++ src/SArray.jl | 4 ++-- src/SVector.jl | 5 +---- test/MVector.jl | 30 ++++++++++++++++++++++++++++++ test/SVector.jl | 13 +++++++++++++ 8 files changed, 86 insertions(+), 29 deletions(-) diff --git a/docs/src/index.md b/docs/src/index.md index 0b4519f1..e75dedbe 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -15,24 +15,3 @@ as [`SizedArray`](@ref) for annotating standard `Array`s with static size inform Further, the abstract [`FieldVector`](@ref) can be used to make fast static vectors out of any uniform Julia "struct". -## Migrating code from Julia v0.6 to Julia v0.7 - -When upgrading code that is depending on **StaticArrays** the following notes may be helpful - -* `chol` has been renamed to `cholesky` and return a factorization object. To obtain the factor - use `C = cholesky(A).U`, just like for regular Julia arrays. - -* `lu` now return a factorization object instead of a tuple with `L`, `U`, and `p`. - They can be obtained by destructing via iteration (`L, U, p = lu(A)`) or by - using `getfield` (`F = lu(A); L, U, p = F.L, F.U, F.p`). - -* `qr` now return a factorization object instead of a tuple with `Q` and `R`. - They can be obtained by destructing via iteration (`Q, R = qr(A)`) or by - using `getfield` (`F = qr(A); Q, R = F.Q, F.R`) - -* `eig` has been renamed to `eigen`, which return a factorization object, rather than - a tuple with `(values, vectors)`. They can be obtained by destructing via iteration - (`values, vectors = eigen(A)`) or by using `getfield` - (`E = eigen(A); values, vectors = E.values, E.vectors`). - -* `unshift` and `shift` have been renamed to `pushfirst` and `popfirst`. diff --git a/docs/src/pages/quickstart.md b/docs/src/pages/quickstart.md index c0bb77dd..602ae023 100644 --- a/docs/src/pages/quickstart.md +++ b/docs/src/pages/quickstart.md @@ -49,6 +49,23 @@ v1[SVector(3,2,1)] === @SVector [3, 2, 1] v1[:] === v1 typeof(v1[[1,2,3]]) <: Vector # Can't determine size from the type of [1,2,3] +# For geometric and computer graphics applications in dimensions 1 to 4, the +# conventional dimension names x,y,z,w can be used to access elements of the +# vector: + +u = SA[1,2,3,4] + +u.x === u[1] +u.y === u[2] +u.z === u[3] +u.w === u[4] + +# The x,y,z and w properties also work to set values in those dimensions: +m6 = MVector(1,2) +m6.x = 10 +# The following is now true +m6[1] === 10 + # Is (partially) hooked into BLAS, LAPACK, etc: rand(MMatrix{20,20}) * rand(MMatrix{20,20}) # large matrices can use BLAS eigen(m3) # eigen(), etc uses specialized algorithms up to 3×3, or else LAPACK diff --git a/src/MArray.jl b/src/MArray.jl index 9c928804..534c350d 100644 --- a/src/MArray.jl +++ b/src/MArray.jl @@ -84,7 +84,7 @@ end if isbitstype(T) return GC.@preserve v unsafe_load(Base.unsafe_convert(Ptr{T}, pointer_from_objref(v)), i) end - v.data[i] + getfield(v,:data)[i] end @propagate_inbounds function setindex!(v::MArray, val, i::Int) @@ -102,7 +102,7 @@ end return v end -@inline Tuple(v::MArray) = v.data +@inline Tuple(v::MArray) = getfield(v,:data) Base.dataids(ma::MArray) = (UInt(pointer(ma)),) diff --git a/src/MVector.jl b/src/MVector.jl index c72e98b2..9eb303b9 100644 --- a/src/MVector.jl +++ b/src/MVector.jl @@ -99,3 +99,24 @@ macro MVector(ex) error("Use @MVector [a,b,c] or @MVector([a,b,c])") end end + +# Named field access for the first four elements, using the conventional field +# names from low-dimensional geometry (x,y,z) and computer graphics (w). +let dimension_names = QuoteNode.([:x, :y, :z, :w]) + body = :(getfield(v, name)) + for (i,dim_name) in enumerate(dimension_names) + body = :(name === $(dimension_names[i]) ? getfield(v, :data)[$i] : $body) + @eval @inline function Base.getproperty(v::Union{SVector{$i},MVector{$i}}, + name::Symbol) + $body + end + end + + body = :(setfield!(v, name, e)) + for (i,dim_name) in enumerate(dimension_names) + body = :(name === $dim_name ? @inbounds(v[$i] = e) : $body) + @eval @inline function Base.setproperty!(v::MVector{$i}, name::Symbol, e) + $body + end + end +end diff --git a/src/SArray.jl b/src/SArray.jl index 5e175079..6ee20760 100644 --- a/src/SArray.jl +++ b/src/SArray.jl @@ -113,10 +113,10 @@ sacollect #################### @propagate_inbounds function getindex(v::SArray, i::Int) - v.data[i] + getfield(v,:data)[i] end -@inline Tuple(v::SArray) = v.data +@inline Tuple(v::SArray) = getfield(v,:data) Base.dataids(::SArray) = () diff --git a/src/SVector.jl b/src/SVector.jl index 598c6eb4..bb039e13 100644 --- a/src/SVector.jl +++ b/src/SVector.jl @@ -36,10 +36,6 @@ const SVector{S, T} = SArray{Tuple{S}, T, 1, S} ## SVector methods ## ##################### -@propagate_inbounds function getindex(v::SVector, i::Int) - v.data[i] -end - # Converting a CartesianIndex to an SVector convert(::Type{SVector}, I::CartesianIndex) = SVector(I.I) convert(::Type{SVector{N}}, I::CartesianIndex{N}) where {N} = SVector{N}(I.I) @@ -117,3 +113,4 @@ macro SVector(ex) error("Use @SVector [a,b,c], @SVector Type[a,b,c] or a comprehension like [f(i) for i = i_min:i_max]") end end + diff --git a/test/MVector.jl b/test/MVector.jl index c4486708..b2211725 100644 --- a/test/MVector.jl +++ b/test/MVector.jl @@ -93,4 +93,34 @@ v = MVector{2,String}(undef) @test_throws ErrorException setindex!(v, "a", 1) end + + @testset "Named field access - getproperty/setproperty!" begin + # getproperty + v4 = @MVector [10,20,30,40] + @test v4.x == 10 + @test v4.y == 20 + @test v4.z == 30 + @test v4.w == 40 + + v2 = @MVector [10,20] + @test v2.x == 10 + @test v2.y == 20 + @test_throws ErrorException v2.z + @test_throws ErrorException v2.w + + # setproperty! + @test (v4.x = 100) == 100 + @test (v4.y = 200) == 200 + @test (v4.z = 300) == 300 + @test (v4.w = 400) == 400 + @test v4[1] == 100 + @test v4[2] == 200 + @test v4[3] == 300 + @test v4[4] == 400 + + @test (v2.x = 100) == 100 + @test (v2.y = 200) == 200 + @test_throws ErrorException (v2.z = 200) + @test_throws ErrorException (v2.w = 200) + end end diff --git a/test/SVector.jl b/test/SVector.jl index bc0c5b51..c7b0add9 100644 --- a/test/SVector.jl +++ b/test/SVector.jl @@ -102,4 +102,17 @@ @test @inferred(convert(SVector, c)) == SVector{2,Int}([1, 2]) @test @inferred(convert(SVector{2}, c)) == SVector{2,Int}([1, 2]) end + + @testset "Named field access - getproperty" begin + v4 = SA[10,20,30,40] + @test v4.x == 10 + @test v4.y == 20 + @test v4.z == 30 + @test v4.w == 40 + v2 = SA[10,20] + @test v2.x == 10 + @test v2.y == 20 + @test_throws ErrorException v2.z + @test_throws ErrorException v2.w + end end