I do feel like the implication that you can make arbitrary vector types is a bad thing. You can only use Vector4 with a very fixed set of types, and adding a new type to that set is a fairly manual process, and not one that we want to send through dynamic dispatch.
…Actually, let's explore this a little further. What's the right way to get a Vector4 of CGFloats? That's definitely something we should support; how much work is it?
…Actually, let's explore this a little further. What's the right way to get a Vector4 of CGFloats? That's definitely something we should support; how much work is it?
Fairly easy with either approach, though a little bit weird because CGFloat itself isn't in the stdlib. Basically, with any of these approaches, you need to define the memberwise accessor and all the arithmetic operations and conversions for each vector size, as well as a zero-initializer. All of these would simply delegate to the underlying Float / Double implementations, however.
So it would end up being ... about as much work as CGFloat itself is; some boilerplate, but mostly it's write-once read-never code.
So the next step is how someone would do that with the generic type. On the one hand, it'd be nice™ to be all uniform like that, but on the other it seems impossible to do it and guarantee performance.
With the generic type (as sketched by @beccadax) , it would be effectively exactly the same. The generic type just shifts all the magic implementations onto the element type, and then calls them through a protocol. It's superficially a layer of indirection, but that layer would all be @_transparent and it should work fine.
It ends up looking basically just like the rejected pattern before we had static-operators-in-protocols where we were going to do global
except that the magic implementation hooks would be on the element type rather than the vector type, and there's a separate definition for each vector length.
I personally don't like it because I think the implementation is gross, but I'd be happy to set that aside if it enables doing stuff that's otherwise hard to do.
The main reason I don’t like Float.Vec4 (for example) is that the relationship between Float and Vec4 isn’t like the relationships other nested types have with the types they’re defined in. Something like UIApplication.LaunchOptionsKey makes sense because LaunchOptionsKey is only meaningful in the context of UIApplication. Vec3 can have any of several element types, so it feels “inside-out” (and would be less discoverable) to start with the element type and then pull the “outer” type out of that.
Vec4<Float> wouldn't have those potential discoverability issues, while still letting us avoid polluting the standard library namespace with many similarly-named top-level IntMxN types.
Can we identify what would be needed to enable a less forced / "gross" implementation?
Yes, please remove the Collection conformance from these types, which is just weird and irritating. In the currently available SIMD (import simd) vector types like float3 the only effect of this conformance is a bunch of irritating perhaps-seemingly-useful-but-actually-totally-useless methods like .map, .filter, .reduce etc. in the list of code completions.
Also, regarding the discussion as a whole: Does it really make sense to consider these SIMD vector and matrix types as being part of some possible future general and generic vector / matrix API?
I mean, for example simd_float3/Vec3.Float has the exact same memory layout as simd_float4/Vec4.Float
(the size, stride and alignment of both is 4x4 = 16 bytes, for good reasons).
And there is no eg simd_float5/Vec5.Float (for good reasons).
But in a general vector / matrix API, there might be equally good (but different) reasons for including a Vec5.Float/Vector<5, Float> and having a Vec3.Float/Vector<3, Float> for which the size, stride and alignment is 3x4 = 12 rather than 4x4 = 16 bytes.
The (crucial) SIMD aspect of this proposal seems to get a bit lost in speculations about a more general vector / matrix API, an idea that might even be impossible to realize, ie it might be too general to be practically useful.
Unless I'm misunderstanding the purpose of the proposed API, it seems necessary to highlight it's SIMD-ness in some way; to make it look less, rather than more, like a general vector / matrix API, in order to avoid confusion and possible future naming conflicts.
Yeah, I don't think Vector is the right name here. C++, Rust and R all have things called vectors that vary in size, and mathematical vectors are not constrained in the number of dimensions either.
I think just SIMD<4, Float> would be fine if it were possible, SIMD4<Float> sounds reasonable until it is. It's easily googleable and will probably be an API to build higher-level accelerated abstractions with rather than use directly in application code anyway.
But … isn't the wish to make them generic like SIMD<4, Float>
(rather than eg Float.SIMD4 or SIMD.Float4 or simd_float4)
yet another example of something that stems from seeing them as general vector types rather than the very specific set of SIMD vector types that they are?
For example: SIMD<5, Float> would not exist / be disallowed, and the implementation of SIMD<3, Float> and SIMD<4, Float> would have to make sure they have the same memory layout (16 byte alignment, stride and size), despite the fact that one has 3 and the other has 4 Float elements, which would be far simpler and less weird without the generics.
Seems to me like any generics used for these types would …
… confuse the user by misguiding their understanding of the types.
… have to be special cased away anyway, by lots of conditional conformances and probably even by compiler magic, ie how to handle let v = SIMD<5, Float>(…), the implementation and alignment requirements of SIMD<3, Float> and SIMD<4, Float> etc.?
There might be, but a couple decades of experience shows that the two things that cover 99.99% of use cases are
vector/matrix 2,3,4
native machine-size vectors, plus half/double length for conversions.
Everything else combined is like 0.01% of use (also, these are very much intended to be low-level building blocks. If you have these (and a macro system), you can build Vector5 pretty easily.
And every gpu or compute programming language in existence calls these things "vec" or "vector". The term of art is computer architecture is "vector". These things are much more like mathematical vectors than what C++ and R's use of "vector" for (in fact, these are mathematical vectors; they are just endowed with some other operations as well).
This leaves you without a particularly good name for the corresponding matrix types (SIMD4x4 is maybe tolerable, but significantly worse than Matrix4x4 or similar).
Looking through the simd_operations, I think that SIMD is the mashup of a vector library, and a parallel primitives library, just because they operate on types with the same shapes.
My opinion is that a swifty importation of the C simd library should split these two objectives, by having SIMD[2|3|4] with the element-wise operations like simd_add, simd_max, simd_sign, etc. and Vector[2|3|4] with the non-element-wise or mathematically vector operations like simd_add, simd_dot, simd_project, etc.
i can think of no reason why anyone would ever want such a thing, or why it would have a different alignment given that matrices are always treated as a vectors of Vec4s, even if they aren’t actually 4 units tall. reaching for packed structures is really a sign that swift needs to get a lot better with raw buffer loads and stores, not that we need a Vec3 with an alignment of 12. Graphics programming is something that has existed for a long time and long ago people figured out vec2, vec3, vec4, mat3x4, mat4, etc was all you really needed and stuff like vec5 is just a distraction. let’s not reinvent the wheel
It is a mashup, but not “just because” they operate on the same shspes. Rather, it’s turned out to be remarkably useful for these two thigs to be treated together. Every client of the library has a subset of operations they use all the time and another subset that’s “obviously” out of scope, but these subsets don’t really overlap for any two clients. So while I think that there may be two protocols here, I do not think that having parallel types buys us much more than a lot of extraneous no-op conversions.
I really like the proposal! Personally, I find graphics programming to be a lot of fun and I agree that
However, I also don't think the syntax Float.Vector4 or Float.Vec4 does not feels very Swift at all in comparison to Vector4<Float>. Using Vector4<Float> or SIMD4<Float> absolutely stays inline with current Swift naming conventions and would keep namespace much clearer and far more searchable with auto-complete than typing Float. and then looking through the massive list that would appear with these nested types. Also, I agree that SIMD types shouldn't be seen as collections, but I disagree that use of < > should equate to collection types only. UnsafePointer and other pointer types already use < > are do not, to me at least, say "this is a collection type" when I seem them. I just see a pointer with an associated subtype. These pointer types may used for collection types, they don't have to be. It's just as easy to use them for other kinds of data.
Since I'm in support of SomeSIMDType<SubType>, I feel I should also comment on the compiler. If the the compiler may be improved to include this new type, I think it would be better in the long run.
I think these are very good questions, but it really feels like they are being asked in a "tape and glue will suffice" way which is the impression I have been getting in reading a number of comments. However, the easy road is not always the best, and when you want to end up with something worth while, it is usually not the best. I'm not saying the working on the compiler is the best way, but I really think it is better to truly investigate it as a serious possibility instead of just writing it off because it involves more work. I agree that the fact that it requires more work may truly be a sign of a deficiency in complexity in the compiler as mentioned by @Karl.
Would it not be better to extend the compiler instead of using what may turn out to be a convenient hack and not the proposed "simpler" change.
Anyway, very excited about the prospect of SIMD being native to Swift--great idea @scanon!
the angle brackets don’t make sense because the “generic parameter” isn’t generic at all. SIMD vector types are a bunch of discrete concrete types, and not all combinations are allowed. it’s misleading because if you have Vec16<Int32> you would fully expect Vec16<Int64> to work as well. And then people would be wondering why Vec16<Int32> is okay but Vec32<Int32> isn’t, but, get this, Vec32<Int16> is. To make it work you would have to define protocols from N = 2, 4, 6, 8, 16, 32, 64 and then conform a particular subset of the integer types to each one. no. just no.
Broadly speaking, I agree with this sentiment, but there are two factors pushing me against this, beyond the fact that I personally find VectorN<T> not especially compelling:
It's not entirely obvious what language / compiler features we actually want to enable this. I can think of a few options, none of them are especially simple, and the simpler ones seem more like hacks than principled solutions. If there were a nice simple answer, I would be more enthusiastic, but I don't see it.
The perfect is the enemy of the good. We don't want to wait to add useful features to the standard library until the language is perfect.
For me, this looks like a manifestation of a problem much more general than this pitch:
There is a strong push towards stability, and there has been pretty much "we can't do that anymore" lately. So I guess it is natural to assume that anything that is decided in the evolution process is set in stone, and that it will be harder and harder to correct minor errors or replace solutions with better alternatives that might rise in the future.
I'm the last to speak against more thoughtful planning, but nobody can be sure how exactly Swift evolves, so it might be that we find a really fantastic design whose only flaw is that it will never be implemented.
But in this special case, I think the pressure isn't that big:
It is not a feature that will be needed by many developers, and I expect that it is feasible to perform a soft migration when slightly better solutions become available.
So could we have UInt32.Vector8 now, but keep the option to replace that when (if) Swift has the ability to express the concept in a better way?
This is one of the reasons that I like T.VectorN; if/when we migrate to some other spelling, we'll necessarily have to keep whatever spelling we pick now around for quite a long transition period. This one has the virtue that it wouldn't leave any deprecated names lying around in the top-level namespace.
as the patron saint of VectorN.T i would like to point out that a transition from T.VectorN to VectorN<T> would be kind of jarring and confusing since you’ve inverted the type hierarchy whereas VectorN.T already looks like VectorN<T> with different punctuation, and that the symbol “VectorN” wouldn’t go away, it would just go from a concrete type to a generic type. i love bikeshedding names i feel so productive