Skip to content
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

Naming for small vectors #24

Closed
c42f opened this issue Sep 5, 2016 · 55 comments · Fixed by #656
Closed

Naming for small vectors #24

c42f opened this issue Sep 5, 2016 · 55 comments · Fixed by #656
Labels
design speculative design related issue feature features and feature requests

Comments

@c42f
Copy link
Member

c42f commented Sep 5, 2016

As discussed at SimonDanisch/FixedSizeArrays.jl#159, I rather liked writing Vec and Mat for small sized arrays in FixedSizeArrays. Perhaps we should try to recover these names for StaticArrays, perhaps not?

Note that SIMD.jl has also started using Vec so there is unfortunately already a naming clash with FixedSizeArrays there @eschnett

@andyferris
Copy link
Member

Also interesting: SIMD.jl is probably closer to VecElement which is in Base.

@andyferris
Copy link
Member

andyferris commented Sep 5, 2016

OK, I'll respond with the history of my decision and we can work on the future from there:

  • I wanted to do multidimensional arrays, but Arr seems kinda silly. It's nice to have something as close to typealias SVector{T} SArray{T,1} as possible but I didn't manage to get there because there are only a small set of operations allowed in a type alias and SArray has a complicated parameter structure (if we can address this over time that is good).
  • There is a history of other packages like ArrayFire.jl has AFArray and DistributedArrays.jl has DArray (each with AFVector and DVector, etc) so it's becoming a little idiomatic in Julia to name things this way.
  • Being careful not to have a package race/war to see who can claim Vec and Mat first (back then I didn't want to clash with FixedSizeArrays either.)
  • There is more than one prefix for different types of object, like SArray and MArray. I'm thinking something like PArray or PtrArray for a pointer-based, fixed-size array.
  • For the sake of being verbose rather than concise (people who know Julia but not StaticArrays.jl might guess that SVector behaves quite similarly to Vector).

@eschnett
Copy link
Contributor

eschnett commented Sep 5, 2016

The name VecElement in Base was chosen because it is shorter than SIMDVectorElement, and because it is more descriptive than VecEl.

If there is an overall consistent naming scheme, then renaming SIMD's types is not out of the question.

@c42f
Copy link
Member Author

c42f commented Sep 5, 2016

Yes, all valid points.

On the subject of verbosity, I feel that Vec is something that gets typed rather a lot, so I'd consider the "small names for commonly used things" rule applies in this case. I also like the "short names for short vectors" pun ;-)

What's the idea behind PArray vs MArray?

@andyferris
Copy link
Member

@eschnett SIMDElement ?

@c42f Would a typealias for Vec be useful?

What's the idea behind PArray vs MArray?

Well, it could be used for C interop (or non-garbage collected arrays with Base.Libc.malloc() etc) or for a hack I'm planning for getting a stack pointer using an LLVM call which allocates stack space and calls a Julia function (allocating an extra stack frame should be faster than garbage collection or even malloc/free, though not as fast as Julia just letting me have a damn stack pointer!).

@andyferris
Copy link
Member

In other words, PArray should be fast like SArray and mutable like MArray.

@c42f
Copy link
Member Author

c42f commented Sep 5, 2016

Oh I see - PArray is an array with a bare pointer inside (user to manage the memory). The compiler will probably (?) have limited insight into that if it's stack allocated in an LLVM call so codegen may not be so good. But it's worth a go, a stack allocated mutable buffer of some kind would be really useful in some cases.

I'm not sure about a typealias in the long run - I think that'll just lead to syntactic fragmentation among users of the library and cause confusion. Could be useful in the short term to see how things will play out.

@andyferris
Copy link
Member

Could be useful in the short term to see how things will play out.

There's always using StaticArrays.FixedSizeArrays for the short term. You can already use Vec there.

@SimonDanisch
Copy link
Member

Well, last year using pointers was a disaster for codegen... And since the compiler devs are discouraging the use of pointers, I'd be surprised if this changed substantially ;) Also, things get ugly very quickly :-D I just hope this damn mutable tuples julep will come soon! :-D

@andyferris
Copy link
Member

I agree Simon, but note that MVector is already implemented purely in terms of pointers (as in getindex and setindex!). Codegen is worse: approximately half the speed of SVector which is unfortunate. But still quite a lot faster than Vector in many cases!

Apart from the fact that it lacks SIMD and might have an extra pointer indirection, it is possible the codegen is worse still for other silly reasons - I'd like to understand this better. But like I said in many cases, MVector is a lot faster than Vector. The obvious advantage is that if you can avoid a heap allocation you can save a lot of time on allocation and GC, and sometimes you really need a mutable object to implement an efficient algorithm. The benchmarks are enlightening.

The mutable tuple thing should resolve all of this and these 3 types will become 1.

@eschnett
Copy link
Contributor

eschnett commented Sep 6, 2016

@andyferris What about SIMDElement? If you suggest that Base.VecElement could be renamed to Base.SIMDElement -- yes, that might be a good idea.

On the other hand, renaming SIMD.Vec to SIMD.SIMDElement is not a good idea, because the SIMD type is a vector, so renaming it to SIMDElement would be confusing.

@andyferris
Copy link
Member

Yes I meant Base.VecElement.

@andyferris
Copy link
Member

andyferris commented Sep 12, 2016

One decent option, could implement ImmutableArrays-style typealias's. Some possibilities

# Option 1, this list is direct from ImmutableArrays.jl
export Vector1,   Vector2,   Vector3,   Vector4,
       Matrix1x1, Matrix1x2, Matrix1x3, Matrix1x4,
       Matrix2x1, Matrix2x2, Matrix2x3, Matrix2x4,
       Matrix3x1, Matrix3x2, Matrix3x3, Matrix3x4,
       Matrix4x1, Matrix4x2, Matrix4x3, Matrix4x4

# Option 2: Some new hybrid FixedSizeArray/ImmutableArray names
typealias Vec3{T} SVector{3,T}
typealias Mat3x3{T} SMatrix{3,3,T,9}
# etc...

# Option 3: Seen this in C++, but I just realized OpenEXR's Imath::V3d is "d" for "double" not "dimensions"
typealias V3d{T} SVector{3,T}

# Option 4, these are probably the correct Imath equivalents in Julia
typealias V3{T} SVector{3,T}
typealias M33{T} SMatrix{3,3,T,9} # or M3x3{T} ? Works beyond dimension 9.

Option 1 can be implemented just like our compatibility layer for FixedSizeArrays, so users type using StaticArrays.ImmutableArrays and they won't need to change old code. But those names aren't as short as they could be. If we want super-short names like V3 we could also make it optional via an extra using StaticArrays.ShortNames.

To be honest, option 4 seems pretty sweet to me.

@andyferris
Copy link
Member

@twadleigh I'll ping you since your opinion could be valuable here after authoring ImmutableArrays.

@andyferris
Copy link
Member

To be honest, option 4 seems pretty sweet to me.

And option 2 is also quite readable.

@SimonDanisch
Copy link
Member

Since I find something like V3 too short and Vector3 too long, I've come to like what we do in GeometryTyeps (which is almost option 2):
https://github.com/JuliaGeometry/GeometryTypes.jl/blob/master/src/typealias.jl#L2

@andyferris
Copy link
Member

andyferris commented Sep 12, 2016

Cool, thanks for that. Do you still find the Vec and Point distinction quite useful, Simon?

I'm not sure why you chose f0 as a suffix? Because we type 1f0 to get a Float32 literal? Seems f would be shorter...

But the short fallbacks for small square matrices like Mat3 seems pretty cool. Not sure how I feel about not having a template parameter, though! Do you find saving that bit of typing useful? For my purposes, I want my code to be dual-number friendly. But we could go with relatively-standard C (or BLAS) suffixes Vec3d, Vec3f, Vec3i, Vec3c and Vec3z, etc...

Or maybe Julia counterparts:

Vec3
Vec3f32
Vec3i
Vec3i64
Vec3i32
Vec3c
Vec3c32

where Float64 is favored as a default (or not, use Vec3f64).

@SimonDanisch
Copy link
Member

Do you still find the Vec and Point distinction quite useful, Simon?

I do, mostly for graphics/plotting them differently ;) But I also like it, that you can read code and immediately know that a point in space is meant.

The list you propose looks agreeable ;)

@andyferris
Copy link
Member

But I also like it, that you can read code and immediately know that a point in space is meant.

I agree that is very nice to read.

The list you propose looks agreeable ;)

I'm leaning towards Vec3{T} to be honest. In most places the Vec3 itself will be sufficient, unless you want to do something specific, in which case having T spelt out for you helps with readability.

@SimonDanisch
Copy link
Member

Well, I'm using mostly Vec3{Float32}, which I find a bit annoying to spell out. But I wouldn't mind adding a typealias in my packages for that.

@andyferris
Copy link
Member

Oh wait, I just saw that :)

OK I think we agree to that. Another thing to consider is Point, but since it is geometric maybe it's definition should begin in a geometry package (which perhaps moves to StaticArrays as the backend as we move forward).

@andyferris
Copy link
Member

andyferris commented Sep 12, 2016

OK in that case having Vec3 and Vec3d/Vec3f could be super useful. It's not very Julian but it is common enough...

@SimonDanisch
Copy link
Member

Another thing to consider is Point, but since it is geometric maybe it's definition should begin in a geometry package

I agree and that's also where it was originally located.. I think there was only a minor reason why I moved it to FixedSizeArrays ;)

@andyferris
Copy link
Member

OK, nice. I will have a closer look at GeometryTypes.

@eschnett
Copy link
Contributor

What about generic types Vec{N,T} and Mat{N,M,T}?

@andyferris
Copy link
Member

We haven't ruled out Vec and Mat yet but would these be typealiases of SVector and SMatrix? I'm not sure Vec and MVector make a nice pair of types.

We unfortunately need Mat{M,N,T,L} unless we can get JuliaLang/julia#18466 implemented.

I have been using StaticArrays for a while now and I don't typically mind typing SVector{N} when I mean arbitrary N but it is rather tiring when I'm doing just 3D geometry.

@eschnett
Copy link
Contributor

You might be able to use Mat{M,N,T} if you define Mat in terms of two nested Vec, or if you use NTuple{N, NTuple{M, T}} as storage type.

@andyferris
Copy link
Member

Right, yes... that is the way FixedSizeArrays does it.

Unfortunately, I wasn't sure this always worked well - we rely on codegen to get rid of a lot of loads in mat.data[i][j], and I noticed it was particularly bad at mutable types where it would pull entire tuples from the heap to the stack and then use just one element. On the other hand, linear indexing is reasonably easy to implement... there's lots of prior art in Julia Base.

In general I've pushed towards maximal performance (because if you want convenience just use Vector), but if we can resolve these issues I'm all in favour. My hope is that the API (and performance for the mutable types) will improve as Julia matures.

@c42f
Copy link
Member Author

c42f commented Sep 13, 2016

Yes, FixedSizeArrays uses a tuple of tuples internally because of julia-0.4 limitations, but it was fairly painful to work with in the implementation, and the 0.4 compiler had a hard time when the nesting exceeded a certain depth (4==MAX_TUPLE_DEPTH? I can't remember exactly). In any case, I'm not particularly keen to go back to the nested implementation if we can help it.

unless we can get JuliaLang/julia#18466 implemented

For anyone following the breadcrumbs, this ended up being a dup of JuliaLang/julia#8322

For extra context, the most common time I'd be typing/reading Vec or Mat would probably be in constructor syntax, eg,

v = Vec(1,2,3)

m = @Mat [1 2; 3 4]

vs = [Vec(i,i+1) for i=1:10]

where the fact that Vec is not a leaf type is not a problem, since the required leaf type will be inferred by type inference on the constructor. So perhaps the fact that the container type is the last type parameter to Vec and Mat isn't a big deal?

@c42f
Copy link
Member Author

c42f commented Sep 13, 2016

For the problem of naming short aliases, it's hard to know how to be very concise but julian about it.

From the Imath library I've grown somewhat fond of V3d ("Vector of three doubles") V3f ("vector of three floats") etc, but speaking of "float" and "double" isn't very idiomatic here. We can just pretend we're following the BLAS convention, I mean, who doesn't love zgebrd, what could be clearer? Um.

Regarding Vec3{Float64} vs Vec{3,Float64} these seem a very similar amount of typing - is there really much benefit to having a Vec3{T} type alias?

V3f64 could work, but it's an awful lot of numbers to be typing for a shortcut.

@andyferris
Copy link
Member

andyferris commented Dec 20, 2016

Here's a thought for easier syntax, using S[1,2,3].

julia> using StaticArrays

julia> abstract S{T}

julia> Base.getindex{N}(::Type{S}, x::Vararg{Any,N}) = SVector{N}(x)

julia> Base.getindex{N,T}(::Type{S{T}}, x::Vararg{Any, N}) = SVector{N,T}(x)

julia> S[1,2,3]
3-element StaticArrays.SVector{3,Int64}:
 1
 2
 3

julia> S{Float64}[1,2,3]
3-element StaticArrays.SVector{3,Float64}:
 1.0
 2.0
 3.0

The thought here was inspired distrubted array which defines things like drandn() that makes a DArray (there is also sprandn() for sparse arrays) so I thought we could take ownership of the s and S prefixes (to match SArray, etc).

We also nicely get that most functions like srandn(Float32, 10) can be @pure functions (for isbits element types) and thus fully type inferrable, which has been a bottle neck thus far. That might be better syntax than @SVector(randn(Float32, 10)) and better than randn(SVector{10,Float32}) - thoughts? It's definitely more brief. On the other hand, having to define new function names really misses the point of multiple dispatch (and the many benefits of a modular interface, e.g. as espoused in this recent blog post).

One downside is that srand() is defined in base for the random seed...

@cdsousa
Copy link

cdsousa commented Dec 20, 2016

I really like the S[] syntax!
But it's sad it cannot be used for comprehensions... S[i for i=1:3]

@c42f
Copy link
Member Author

c42f commented Dec 20, 2016

Ok, the S[1,2] syntax is a pretty great hack, I like this a lot. S is too short to be sensible, but SA[1,2,3] might do. Or Static[1,2,3]?

@cdsousa
Copy link

cdsousa commented Dec 20, 2016 via email

@c42f
Copy link
Member Author

c42f commented Dec 20, 2016

In isolation the short form is the prettiest, no doubt about it.

However, I just spent a little time looking at this in the context of a larger chunk of code which lightly uses SVector, and the S form is just too short. SA looks a lot better in that context, and Static is also ok.

@SimonDanisch
Copy link
Member

Hm I'm not sure if I'm a big fan of this. As it is, I already regularly confuse indexing with typed array construction. Also it just looks like the result should be a Julia array. Fun fact, a year (or two?) ago I proposed this exact construction syntax, but after confusing this syntax a couple of times, my opinion has changed.
I'd be much more in favor of it, if we could have our own brackets, e.g.:

{1,2,3}
Float32{1,2,3}

@andyferris
Copy link
Member

andyferris commented Dec 20, 2016

But it's sad it cannot be used for comprehensions... S[i for i=1:3]

Right... lowering of comprehensions is extremely direct to making an array:

julia> f() = S[i for i=1:3]
f (generic function with 1 method)

julia> @code_lowered f()
LambdaInfo template for f() at REPL[1]:1
:(begin 
        nothing
        SSAValue(0) = (Main.colon)(1,3)
        SSAValue(1) = (Base.length)(SSAValue(0))
        SSAValue(2) = (Core.Array)(Main.S,SSAValue(1))
        #temp#@_2 = 1
        #temp#@_3 = (Base.start)(SSAValue(0))
        #temp#@_4 = (Core.typeassert)((Base.convert)((Core.typeof)(SSAValue(1)),0),(Core.typeof)(SSAValue(1)))
        8: 
        unless #temp#@_4 != SSAValue(1) goto 27
        SSAValue(3) = #temp#@_4 + 1
        #temp#@_4 = (Core.typeassert)((Base.convert)((Core.typeof)(SSAValue(1)),SSAValue(3)),(Core.typeof)(SSAValue(1)))
        SSAValue(4) = (Base.next)(SSAValue(0),#temp#@_3)
        #temp#@_6 = (Base.start)(SSAValue(4))
        SSAValue(5) = (Base.indexed_next)(SSAValue(4),1,#temp#@_6)
        i = (Core.getfield)(SSAValue(5),1)
        #temp#@_6 = (Core.getfield)(SSAValue(5),2)
        SSAValue(6) = (Base.indexed_next)(SSAValue(4),2,#temp#@_6)
        #temp#@_3 = (Core.getfield)(SSAValue(6),1)
        #temp#@_6 = (Core.getfield)(SSAValue(6),2)
        SSAValue(7) = i
        $(Expr(:inbounds, true))
        (Base.setindex!)(SSAValue(2),SSAValue(7),#temp#@_2)
        $(Expr(:inbounds, :pop))
        #temp#@_2 = #temp#@_2 + 1
        25: 
        goto 8
        27: 
        return SSAValue(2)
    end)

The things you learn. On the other hand we can do

julia> f() = S[(i for i=1:3)]
WARNING: Method definition f() in module Main at REPL[1]:1 overwritten at REPL[4]:1.
f (generic function with 1 method)

julia> @code_lowered f()
LambdaInfo template for f() at REPL[4]:1
:(begin 
        nothing
        SSAValue(0) = Main.S
        #1 = $(Expr(:new, :(Main.##1#2)))
        SSAValue(1) = #1
        SSAValue(2) = (Main.colon)(1,3)
        SSAValue(3) = (Base.Generator)(SSAValue(1),SSAValue(2))
        return (Main.getindex)(SSAValue(0),SSAValue(3))
    end)

and so could overload getindex(::Type{S}, ::Generator)...

The only thing I'm worried about is that inference will not treat not treat the Generator as Const (because colon is not marked @pure and most functions in the generators will capture local variables). However, a future version of Julia with improved inference/inlining might be able to get around all of these issues, but for now that's out.

@andyferris
Copy link
Member

We could also do an @SA macro, like @fsa.

@c42f
Copy link
Member Author

c42f commented Dec 21, 2016

the short form reminds of non-standard string literals

Right, point taken, but string literals don't take part in the same namespace that normal types and functions do (at least not until they've been name-mangled). Taking a one-letter name for a relatively little-used feature like string macros seems a bit more reasonable than taking it in this case.

Simon wrote

I'd be much more in favor of it, if we could have our own brackets, e.g.:
{1,2,3}
Float32{1,2,3}

Yes, some other kind of brackets would be great... but I wouldn't want to bless them for use with static arrays.

Hmm, now if only we could have "bracket macros" of some kind. Then S⟨1,2,3⟩ Could mean @S_mathangle(1,2,3) or something. As with string macros, this would hugely multiply the number of literals which can be expressed.

Of course, we've not got enough types of ascii brackets - all the existing ones are taken for important core features, and we already suffer from the syntax pun which is typed array construction vs indexing.

@c42f
Copy link
Member Author

c42f commented Dec 21, 2016

little-used feature like string macros

or perhaps I should clarify - this is a feature which is very commonly used, but not super commonly extended, so the _str namespace isn't very cluttered.

@c42f
Copy link
Member Author

c42f commented Dec 26, 2016

Well, just for fun, here's a branch which adds "angle bracket macros" to the parser:

https://github.com/c42f/julia/commits/extra-brackets

Demo:

julia> macro S_mathangle(exs...)
           quote
               SVector($exs)
           end
       end
@S_mathangle (macro with 1 method)

julia> S⟨1,2.< S⟨1.5, 1.52-element StaticArrays.SVector{2,Bool}:
  true
 false

To ease the pain of typing these special unicode brackets, I added \< and \> shortcuts, which changed things from "wow, typing \langle and \rangle everywhere is terrible" to "special brackets might be good for something, though maybe not this" :-)

@andyferris
Copy link
Member

andyferris commented Dec 26, 2016 via email

@c42f
Copy link
Member Author

c42f commented Dec 26, 2016

Yes I was thinking more about this, and came to the conclusion that having it as a macro is kind of bizarre and useless because it doesn't achieve much over normal macros, other than a relatively minor difference in notation. I'd hope for something more powerful than that.

Like you say, it might be more interesting to desugar these to a function call. Allowing juxtaposition with an expression like normal square brackets would allow many packages to cooperate using the additional bracket types, just like [] desugars to getindex. I guess I'm not super excited about that either though, it just doesn't seem to provide the power that should justify additional syntax.

@cdsousa
Copy link

cdsousa commented Aug 31, 2017

Related: JuliaLang/julia#23519

@cdsousa
Copy link

cdsousa commented Sep 1, 2017

I've just tried to allow

@SArray[1 2; 3 4] * @SArray[1,2]

through JuliaLang/julia#23547

@c42f
Copy link
Member Author

c42f commented Jul 19, 2019

I feel like this was never resolved in a fully satisfying way so I finally revisited this and implemented the SA thing for discussion - see #633

One thing that makes this more compelling in 1.0 is that the hvcat version

SA[1 2
   3 4]

can be made inferrable as SArray{Tuple{2,2},Int64,2,4}, despite the inconvenient way that it's lowered.

@c42f c42f added design speculative design related issue feature features and feature requests labels Aug 1, 2019
@andyferris
Copy link
Member

cc @EvertSchippers @MeganDawson

c42f added a commit that referenced this issue Sep 15, 2019
This gives a way to construct an `SVector` and `SMatrix` with a precise
eltype, but without typing a verbose type name.

I think this fixes #24 to the extent that it can be fixed.

Also add some quickstart docs for `SA` and the new type aliases.
@c42f c42f closed this as completed in #656 Sep 27, 2019
c42f added a commit that referenced this issue Sep 27, 2019
This gives a way to construct an `SVector` and `SMatrix` with a precise
eltype, but without typing a verbose type name.

I think this fixes #24 to the extent that it can be fixed.

Also add some quickstart docs for `SA` and the new type aliases.
@c42f c42f mentioned this issue Oct 22, 2019
oschulz pushed a commit to oschulz/StaticArrays.jl that referenced this issue Apr 4, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
design speculative design related issue feature features and feature requests
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants