Two questions about your library:
- why'd you go with
loadinstead of aninit? - thoughts about your experience with the return type of
sumbeingNinstead of a wider type (whenNisInt8, it's really easy for this to overflow)?
Two questions about your library:
load instead of an init?sum being N instead of a wider type (when N is Int8, it's really easy for this to overflow)?tuples don’t have inits
.
again,, the list is of operations, not spellings. the spellings come from a lot of “Swift-being-Swift” reasons like this that honestly wouldn’t apply in this situation
Fair. I guess my question is really, "why'd you use a tuple", but I probably know the answer already, and it's probably outside the scope of this post.
Every method in this list came from duplicated blocks of code in a project that got factored out. (also why you might notice “missing” methods: methods like vol(_ v:V4) are missing because I never needed them.) So every time I wound up using sum was when i was replacing an ax + ay + az somewhere with a Math.sum(a). so if sum was going to overflow, the code it was replacing was going to as well, which it wasn’t, because i only ever really use this with Int(32) or Float
I don't mind the length, but the dot bothers me here. The semantics of . usually mean that the type is contained within Int, etc... somewhere, but here we have the opposite, the vector is made of Ints. I definitely don't really ever read . as "of".
I would expect to see Vector16<Int8> as the spelling, but I can guess that there are compiler issues around that. I would support special casing this in the compiler until a more general feature can replace it.
It’s not just a compiler issue. The language really has no way to make that work while backing the implementations with the builtin llvm types. So you can have the syntax you want but be at the mercy of the autovectorizer, or you can have the types you want with a name that’s slightly less pretty.
I'd expect the the API to look like Vector16<Int8> as well. I know there's no way to have each combination of API and specialized scalar type correspond to an LLVM builtin using the existing type system, but can't we extend the compiler to do it? As long as we can express every API in the type system, the backend is just an implementation detail and I believe it should not interfere with API design decisions.
In TensorFlow we use a #tfop interface to create arbitrary builtins, and have SIL mandatory passes to lower them.
I should also point out that Vector16<T> would have the drawback that it has a totally different set of operations depending on whether T is an Integer or FloatingPoint type, which is pretty weird. Vectors are not just collections, and it’s generally a mistake to treat them like that.
It should be expressible using generic constraints, right?
I definitely agree.
It would be generally useful for generic types to have a different layout based on their generic parameters. Also for such types to restrict the generic parameters to a fixed set of concrete types (there might also be a way to do it with sealed protocols). That would also allow generic algorithms to use vectors - e.g. if you have an Array of X, being able to process those elements with simd operations. It’s a shame that the language right now cannot support such an interface.
Also, i think the initialiser should allow creating a vector from any sequence of numbers. That would allow you to create vectors from slices of arrays, instead of just whole arrays.
Is it possible to construct a vector in-place for contiguously-stored numbers? Currently the array initialiser copies everything.
Just a word of warning on this - while I also find the clarity of the Float.Vector4 name to be high (and this presumably also makes it easier to write generic code across different element types), I am also concerned that this will lead to exactly what you promise above: people will define local type aliases to make the names palatable for their own code. If that is the case, then the stdlib should just provide the "right" type aliases. Failing to do so will cause a proliferation of similer-but-differently named aliases.
Is there a disadvantage to providing Float.Vector4 and the Floatx4 and Int8x4 type aliases?
-Chris
The only issue I see with the standard library providing the typealiases is I think the ‘correct’ name heavily depends on context. In maths-heavy rendering code, for example, I want a very short name - even Vec3 or Vec4. The fact that the type occurs every second line or so justifies the brevity.
On the other hand, code that only occasionally deals with these types probably wants to be verbose - it can’t assume, for example, that a Vec3 will be Float.Vector3, since is could equally be UInt32.Vector3. It might decide that Vector3f is descriptive enough, though.
I’m not sure there’s a single ‘short’ name for each of these types that satisfies every single usage, which is why I’m okay with only having the long name around. I’m not convinced it’s such a bad thing if codebases that use these types frequently enough to warrant typealiases do define their own names. That’s not to say I’m opposed to e.g. Floatx4 (although I personally find the name slightly ugly) being in the standard library; just to say that even if it were I likely still wouldn’t use that name, and I expect I wouldn’t be alone in that.
Is there a disadvantage to providing
Float.Vector4and theFloatx4andInt8x4type aliases?
The only real disadvantage would be noise. There's something to be said for having a single canonical stdlib name for a type, so that even if people are adding typealiases in their code, they're all typealiasing the same thing, so no one is stuck asking "wait, is Floatx4 the same as Vector.Float4?" when reading the typealias.
We would need to have a few of these to cover all the names that people are likely to want. Off the top of my head, people are likely to want both Floatx4, Vec4f, but I'm sure there will be a few other requests as well.
I don't agree that the desired spelling is particularly contextual here—Int, Float, and so forth can't reasonably be shortened, and the rest of the spelling comes down to expressing the size and layout of elements.
Following the conventions of parameterized generics would be consistent with this conceptual model. If it wouldn't be a good conceptual model in the case of SIMD types, then that would be a reason to oppose the spelling, but (as is the case elsewhere in the standard library) terseness alone shouldn't trump clarity.
Having said that, a good reason to choose a terser spelling like Vec over Vector would be if we decide the semantics of SIMD types don't correspond closely enough to the semantics of their mathematical inspirations (which are the semantics you'd expect for a type spelled Vector, or Matrix, or Quaternion). This is basically the same situation that arose in the past in conversations about whether to go with Integer instead of Int and so on.
If we want to avoid a "proliferation of similar-but-differently-named aliases" then we should probably strive for solutions besides including similar-but-differently-named aliases of our own. ![]()
^^^this!!! adding more typealiases to the standard library seems a lot like creating the exact problem it’s trying to solve
That is unfortunate.
In that case, I would prefer @xwu's Int8x16 spelling to Int8.Vector16 because I still find the dot confusing. If we absolutely need verbosity, then we could do Vector16OfInt8 or something else without the dot.
Alternatively, we could dig a little deeper and give Swift a notion of dimension, which IMO is something it is missing. We have the concept of a linear continuum (e.g. range), but we can't easily represent that something like a UIColor can be decomposed into multiple axes of linear continuums.
Some ideation:
let x:Int8 x 16 = ...
let y:Int8[16] = ...
let z:Vector16 of Int8 = ...
The advantage of the purely symbolic versions (e.g. Int8[16]) is that they naturally extend to matrices, tensors, etc... without having to guess what we named those. It could also be extended to arbitrary types at some point, but without the simd built-in backing, so if I want a String[3], I can have it, but it won't be accelerated like the simd vectors are, just a fixed-sized array.
The Int8.Vector16 style nested types are the best option I've seen so far because they don't pollute the top level namespace for a fairly niche feature. If you go the Int8x16 way, people will get a crazy autocomplete list every time they go to use an Int. So, if Int8.Vector16 is not acceptable, then I would suggest either nesting them under a SIMD type (e.g. SIMD.Int8x16) or into a module (or submodule, if they were implemented) that needs to be explicitly imported.
it’s fine we’ll just add it to the rationales section under "aids discoverability"
I find it to be quite the reverse. Int8x16 could be read as either a vector of 16 Int8s or an 8 x 16 matrix of Ints. I think the NxM notation should be reserved for describing the dimensions of a matrix and something else should be used to denote the size of a vector.
Oh, that is a really good point, I hadn't thought of Matrices of Ints. I would still like to avoid the dot if possible, whatever the name ends up being.
One point is that if we go with syntax that describes the dimension (e.g. Int8 x 16) then we lose the ambiguity because of the required space. I can see that that is more work to implement/design though...
Ultimately, I would love for Swift to gain some notion of dimension at the core level (regardless of syntax). Swift has tried to pragmatically pull the best elements from different types of languages (e.g. object oriented, functional) and I would love to see it do the same with something like J/APL style languages. We wouldn't want the extreme terseness, of course, but there are very powerful things you can do when you can easily pull apart and recombine things by dimension (map, reduce, and filter are really just a special case subset of things that emerge naturally in a language like J). I would be most interested to see how it would compose with the ideas of protocols.
Again, that is more work, and it probably makes sense to choose a name that we can use now, even if we later upgrade to a syntax based approach. It still makes sense to think about it a little now, so we don't accidentally paint ourselves into a corner and make improvements impossible later on.
My current favorite is the similar-to-c approach of Int8[16]