Skip to content

Commit

Permalink
Named element access with .{x,y,z,w} for SVector and MVector (#980)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
c42f authored Dec 18, 2021
1 parent fa17430 commit c1620f0
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 29 deletions.
21 changes: 0 additions & 21 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
17 changes: 17 additions & 0 deletions docs/src/pages/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/MArray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)),)

Expand Down
21 changes: 21 additions & 0 deletions src/MVector.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 2 additions & 2 deletions src/SArray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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) = ()

Expand Down
5 changes: 1 addition & 4 deletions src/SVector.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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

30 changes: 30 additions & 0 deletions test/MVector.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
13 changes: 13 additions & 0 deletions test/SVector.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit c1620f0

Please # to comment.