Having to reach through unsafe APIs to access an underlying buffer pointer doesn't really qualify as ability to dynamically index something. By that logic, you can claim that you can dynamically index bytes in a string using integers. We're talking about the normal, safe, public API surface that the type exposes to users.
extension (Int, Int, Int, Int) {
public subscript(index: Int) -> Int {
get {
precondition(0..<4 ~= index)
return withUnsafeBufferPointer(self) { $0[index] }
}
set {
precondition(0..<4 ~= index)
withUnsafeMutableBufferPointer(&self) { $0[index] = newValue }
}
}
This is perfectly safe, it's just impossible to generalize without having the number of elements available. It's also currently impossible to write the _modify
version.
The fact that it's perfectly safe in practice doesn't change the fact that you're using an unsafe API. It's right there in the name. The point I was making that's being missed is that tuples do not provide that today—you had to write that yourself. (And it's not even valid Swift today—tuple extensions are experimental.)
You also can't write the same thing for heterogeneous tuples, without introducing some sort of wrapper sum type for the returned value.
Reading through this pitch thread, it seems that there isn't really an ideal solution to implement this sugar.
And I don't agree yet that the problem it tries to solve is big enough. My recommendation would be to first gain more insights about usage of InlineArrays in practice: how common are they? How difficult are they to write? How often do you need to nest them?
And then see if there is enough cause for adding the sugar.
Thanks for the ongoing feedback. I've updated the PR with some of the ideas discussed here:
Flattened version: [5 x 5 x Int]
bumped from Alternative to Future Direction (since it could in theory be introduced subsequently). I'd be interested in thoughts on whether it should actually be the proposal – there is little difference from a parsing perspective.
Choice of brackets: InlineArray
has a lot in common with tuples – especially in sharing "copy on copy" behavior, unlike regular Array
. So (5 x Int)
may be an appropriate alternative to the square brackets, echoing this similarity.
Concern when x
is a variable used with the sugar: Since x
cannot follow another identifier today, [x x Int]
is unambiguous,[1] but would clearly be hard to read. This is likely a hypothetical concern rather than a practical one. While x
is used often in scratch code for a local variable, a more meaningful name is usually preferable, and this would be especially the case if it is found being used for the size of an array literal. In addition, while i
, j
, or n
are often legitimate counters that might be suited to the size of an array, x
is generally not used for such things.
or even
[x x x]
, sincex
can be a type name, albeit one that defies Swift's naming conventions. ↩︎
I don’t think this is true. x
is quite common together with y
and z
in e.g. graphics and game code. [x x [y x T]]
(and [x x y x T]
for the flattened version) is absolutely something that people will write.
x/y/z are extremely common in graphics code to refer to the components of a vector in a Cartesian coordinate system. But in your [x x [y x T]]
example, x
doesn't seem to fulfill that role, but is instead acting as the counter of something, which strikes me as unusual. I can't think of a common use case where I would use x
in that role.
I can see graphics code using x
for literal initialization:
let vector: [3 x Int] = [x, y, z]
Or writing a .x
instance property to access those components from an InlineArray
(like SIMD3
does):
let length = sqrt(pow(vector.x, 2) + pow(vector.y, 2) + pow(vector.z, 2))
Even as the name for an array containing all the positions of something (like a group of objects) along the X axis:
let x: [N_PARTICLES x Double] = .init { Double.random(in: 0...1) }
let y: [N_PARTICLES x Double] = .init { ... }
...
Also to refer to specific pixel positions when indexing a texture, but the texture size would more likely be described using something like width
and height
instead:
let descriptor = MTLTextureDescriptor.texture2DDescriptor(
pixelFormat: .bgra8Unorm,
width: width,
height: height,
mipmapped: false
)
It's more common to use width
/height
/depth
than x
/y
/z
to dispatch threadgroups in a compute kernel as well.
In the context of graphics programming in particular, using x
as a counter (specifically, for the dimension of something) seems odd to me, as it would clash with other established uses of x
.
Is there a reason to avoid curly brackets syntax for such type?
Personally, I don’t think the strongest argument against using x is that it might be confusing, it’s that it feels so incredibly hacky and fundamentally unserious.
Beauty is in the eye of the beholder.
To me anything other than these will feel very ugly and unintuitive.
[m x T]
[m x [n x T]]
[m T]
[m [n T]]
[m]T
[m][n]T
I thank @tera for bringing up the last two.
I'm leaving this thread.
Mathematically, the set of arrays of fixed length n
is the set Int ** n
where x ** y
means pow(x, y)
. Hence, I suggest [Int ** n]
which is both correct mathematically, doesn't cause ambiguity with current symbols (** is a new operator) and uses familiar notation from Python. **
could be replaced by something else that represents power operator.
And do be honest, the braces in the type should be removed - use something and be just Int ** n
For the sake of exploring all options:
let x: [Int, count: 10]
I am wholly unconvinced that a shorthand for repeated values is needed, but:
let x = [10, count: 10]
Could be a generally applicable syntax for array literals.
I didn’t notice before, but I want to point out that this is the wrong unicode symbol. The n-ary times operator \u{2a09}
indicates a repeated operation, similar to ∑ for sums and ∏ for products.
The correct symbol here is the multiplication sign \u{d7}
, which looks like “×” in text and “×
” in code, thus[5 × Int]
for the example.
Traditionally, curly braces tend to more indicate a set than an array e.g. var x: { Int }
looks more like var x: Set<Int>
to me. Which is a different proposal (and one that personally I find kind of appealing, except var x = {1, 2, 3}
probably can't work because of closures, and really that would be the more valuable sugar).
Keep in mind that anything that can be parsed as an operator may be used by someone somewhere since Swift allows the definition of custom operators. Thus, any suggestion of [n ** T]
(where **
could be replaced by anything that's parsable as an operator) is by definition ambiguous, because it could be interpreted as an array literal containing the single element expression n ** T
.
If we wanted to choose something that's already valid in the operator space, I believe we would have to make the claim that "we don't think anyone else is using this operator today, and we're comfortable squatting on it now and indefinitely into the future". That feels like a bold claim to make, at least for something with precedent like **
(and that precedent has a completely different meaning).
A question - why is ×
symbol pushed so hard? It is a Unicode symbol and I am not aware there shortcuts for it on Windows or Linux (no idea if mac has it either). Arrays of fixed length are a basic data type and should be easy to type.
Not to mention that ×
or *
are wrong mathematically.
The notatation suggested by @orobio is correct mathematically and doesn't require using symbols that require unreasonable effort to type.
But I did not suggest n ** T
. It is both confusing and is wrong mathematically. I've suggested [T ** n]
(T ** n
actually, as it represents T x ... x T
(n times), [T ** n]
would be confusing as an array of T ** n
. If T ** n is too confusing, perhaps (T ** n)
should be used and not [T ** n]
).
Can operators be defined on types by users? If no - what is the problem then?
You can define an operator that takes a metatype argument.
The order doesn't make a difference because identifier operator number
or number operator identifier
both have valid parses as expressions today. Whatever syntax is chosen for this sugar needs to fit into expressions of the form type '(' args ')'
used to initialize an instance of a type. Variations of the form [T ** n](...)
or [n ** T](...)
or (T ** n)(...)
all already have meanings in Swift.
For now, doesn’t T
have to be spelled T.self
? Of course, there’s desire to change that.