Skip to content

Commit c1620f0

Browse files
authored
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.
1 parent fa17430 commit c1620f0

File tree

8 files changed

+86
-29
lines changed

8 files changed

+86
-29
lines changed

docs/src/index.md

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,3 @@ as [`SizedArray`](@ref) for annotating standard `Array`s with static size inform
1515
Further, the abstract [`FieldVector`](@ref) can be used to make fast static vectors
1616
out of any uniform Julia "struct".
1717

18-
## Migrating code from Julia v0.6 to Julia v0.7
19-
20-
When upgrading code that is depending on **StaticArrays** the following notes may be helpful
21-
22-
* `chol` has been renamed to `cholesky` and return a factorization object. To obtain the factor
23-
use `C = cholesky(A).U`, just like for regular Julia arrays.
24-
25-
* `lu` now return a factorization object instead of a tuple with `L`, `U`, and `p`.
26-
They can be obtained by destructing via iteration (`L, U, p = lu(A)`) or by
27-
using `getfield` (`F = lu(A); L, U, p = F.L, F.U, F.p`).
28-
29-
* `qr` now return a factorization object instead of a tuple with `Q` and `R`.
30-
They can be obtained by destructing via iteration (`Q, R = qr(A)`) or by
31-
using `getfield` (`F = qr(A); Q, R = F.Q, F.R`)
32-
33-
* `eig` has been renamed to `eigen`, which return a factorization object, rather than
34-
a tuple with `(values, vectors)`. They can be obtained by destructing via iteration
35-
(`values, vectors = eigen(A)`) or by using `getfield`
36-
(`E = eigen(A); values, vectors = E.values, E.vectors`).
37-
38-
* `unshift` and `shift` have been renamed to `pushfirst` and `popfirst`.

docs/src/pages/quickstart.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,23 @@ v1[SVector(3,2,1)] === @SVector [3, 2, 1]
4949
v1[:] === v1
5050
typeof(v1[[1,2,3]]) <: Vector # Can't determine size from the type of [1,2,3]
5151

52+
# For geometric and computer graphics applications in dimensions 1 to 4, the
53+
# conventional dimension names x,y,z,w can be used to access elements of the
54+
# vector:
55+
56+
u = SA[1,2,3,4]
57+
58+
u.x === u[1]
59+
u.y === u[2]
60+
u.z === u[3]
61+
u.w === u[4]
62+
63+
# The x,y,z and w properties also work to set values in those dimensions:
64+
m6 = MVector(1,2)
65+
m6.x = 10
66+
# The following is now true
67+
m6[1] === 10
68+
5269
# Is (partially) hooked into BLAS, LAPACK, etc:
5370
rand(MMatrix{20,20}) * rand(MMatrix{20,20}) # large matrices can use BLAS
5471
eigen(m3) # eigen(), etc uses specialized algorithms up to 3×3, or else LAPACK

src/MArray.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ end
8484
if isbitstype(T)
8585
return GC.@preserve v unsafe_load(Base.unsafe_convert(Ptr{T}, pointer_from_objref(v)), i)
8686
end
87-
v.data[i]
87+
getfield(v,:data)[i]
8888
end
8989

9090
@propagate_inbounds function setindex!(v::MArray, val, i::Int)
@@ -102,7 +102,7 @@ end
102102
return v
103103
end
104104

105-
@inline Tuple(v::MArray) = v.data
105+
@inline Tuple(v::MArray) = getfield(v,:data)
106106

107107
Base.dataids(ma::MArray) = (UInt(pointer(ma)),)
108108

src/MVector.jl

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,24 @@ macro MVector(ex)
9999
error("Use @MVector [a,b,c] or @MVector([a,b,c])")
100100
end
101101
end
102+
103+
# Named field access for the first four elements, using the conventional field
104+
# names from low-dimensional geometry (x,y,z) and computer graphics (w).
105+
let dimension_names = QuoteNode.([:x, :y, :z, :w])
106+
body = :(getfield(v, name))
107+
for (i,dim_name) in enumerate(dimension_names)
108+
body = :(name === $(dimension_names[i]) ? getfield(v, :data)[$i] : $body)
109+
@eval @inline function Base.getproperty(v::Union{SVector{$i},MVector{$i}},
110+
name::Symbol)
111+
$body
112+
end
113+
end
114+
115+
body = :(setfield!(v, name, e))
116+
for (i,dim_name) in enumerate(dimension_names)
117+
body = :(name === $dim_name ? @inbounds(v[$i] = e) : $body)
118+
@eval @inline function Base.setproperty!(v::MVector{$i}, name::Symbol, e)
119+
$body
120+
end
121+
end
122+
end

src/SArray.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,10 @@ sacollect
113113
####################
114114

115115
@propagate_inbounds function getindex(v::SArray, i::Int)
116-
v.data[i]
116+
getfield(v,:data)[i]
117117
end
118118

119-
@inline Tuple(v::SArray) = v.data
119+
@inline Tuple(v::SArray) = getfield(v,:data)
120120

121121
Base.dataids(::SArray) = ()
122122

src/SVector.jl

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,6 @@ const SVector{S, T} = SArray{Tuple{S}, T, 1, S}
3636
## SVector methods ##
3737
#####################
3838

39-
@propagate_inbounds function getindex(v::SVector, i::Int)
40-
v.data[i]
41-
end
42-
4339
# Converting a CartesianIndex to an SVector
4440
convert(::Type{SVector}, I::CartesianIndex) = SVector(I.I)
4541
convert(::Type{SVector{N}}, I::CartesianIndex{N}) where {N} = SVector{N}(I.I)
@@ -117,3 +113,4 @@ macro SVector(ex)
117113
error("Use @SVector [a,b,c], @SVector Type[a,b,c] or a comprehension like [f(i) for i = i_min:i_max]")
118114
end
119115
end
116+

test/MVector.jl

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,34 @@
9393
v = MVector{2,String}(undef)
9494
@test_throws ErrorException setindex!(v, "a", 1)
9595
end
96+
97+
@testset "Named field access - getproperty/setproperty!" begin
98+
# getproperty
99+
v4 = @MVector [10,20,30,40]
100+
@test v4.x == 10
101+
@test v4.y == 20
102+
@test v4.z == 30
103+
@test v4.w == 40
104+
105+
v2 = @MVector [10,20]
106+
@test v2.x == 10
107+
@test v2.y == 20
108+
@test_throws ErrorException v2.z
109+
@test_throws ErrorException v2.w
110+
111+
# setproperty!
112+
@test (v4.x = 100) == 100
113+
@test (v4.y = 200) == 200
114+
@test (v4.z = 300) == 300
115+
@test (v4.w = 400) == 400
116+
@test v4[1] == 100
117+
@test v4[2] == 200
118+
@test v4[3] == 300
119+
@test v4[4] == 400
120+
121+
@test (v2.x = 100) == 100
122+
@test (v2.y = 200) == 200
123+
@test_throws ErrorException (v2.z = 200)
124+
@test_throws ErrorException (v2.w = 200)
125+
end
96126
end

test/SVector.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,17 @@
102102
@test @inferred(convert(SVector, c)) == SVector{2,Int}([1, 2])
103103
@test @inferred(convert(SVector{2}, c)) == SVector{2,Int}([1, 2])
104104
end
105+
106+
@testset "Named field access - getproperty" begin
107+
v4 = SA[10,20,30,40]
108+
@test v4.x == 10
109+
@test v4.y == 20
110+
@test v4.z == 30
111+
@test v4.w == 40
112+
v2 = SA[10,20]
113+
@test v2.x == 10
114+
@test v2.y == 20
115+
@test_throws ErrorException v2.z
116+
@test_throws ErrorException v2.w
117+
end
105118
end

0 commit comments

Comments
 (0)