[Pitch] InlineArray type sugar

After reading this several times , I’m starting to agree that I don’t think that any special sugar syntax should be introduced to sugar this feature.

The Aesthetically pleasing options seem to be colliding with current syntax/varianles. The use of “x” introduces a new keyword and creates ambiguity, the use of “of” introduces a new keyword that might take an already used variable name. We can’t use Int[5] because that’s already calling a potential static subscript. Using * is interesting but feels like it collides even though you can’t currently multiply a number with a Type. For some reason people are opposed to using the Rust syntax [Int; 5] seemingly because it’s not pretty despite it being prior-art. Other operators like isn’t easy to type on non macOS systems.

I would suggest actually something radically different like [Int]5 as it steps on no toes but isn’t aesthetically pleasing.

But the question is this a problem that needs to be solved ?.

You can currently express what is needed in the language with fair ease.

I was surprised to see that the post that I am replying to got over 30 likes. Which indicates to me a lot more support for not solving this problem is pretty popular — twice the likes as the proposal.

2 Likes

Int.self.self, etc is equivalent to Int.self, but those are not equivalent to Int.

1 Like

This bullet point is not entirely obvious as we can't write [Int : 5] today.


Want to add a "keywordless" approach as an alternative:

[5 Int] = [1, 2, 3, 4, 5]
[_ Int] = [1, 2, 3, 4, 5]
[5 _] = [1, 2, 3, 4, 5]
[_ _] = [1, 2, 3, 4, 5]
[_ 99] // [99, 99, 99, 99, 99]
[5 99] // [99, 99, 99, 99, 99]

[5; Int] is not too bad either.

4 Likes

The point was that today, Int[5] and Int[5].self are both legal, and in fact evaluate in the same way. So if this syntax were introduced, there'd be ambiguity even in expression position over the expression Int[5].self.

1 Like

Could you show an example?

1 Like

This comment above is the example. If you write let x = Int[4].self instead of let x = Int[4], it would mean the same thing, because every value in Swift has an implicit .self that refers to that value.

4 Likes

So Int[5].self would be either the result of static subscript (with a redundant self) or the inline array type itself. Interesting. Indeed ambiguous.

1 Like

Any postfix syntax would naturally compose "inside out", since Foo[a][b] would parse as (Foo[a])[b], which would be InlineArray<b, InlineArray<a, Foo>>, which would mean that subscripts on values apply in the opposite order, foo[b][a]. That is why InlineArray puts the count to the left. If we do make a sugar syntax for it, I would strongly prefer we keep the count on the left to naturally maintain symmetry between nested array type dimensions and subscript indices (without special parsing hacks to invert the dimensions).

20 Likes

[N; Int] and [N * Int] are both acceptable to me, but I really do not want to repeat C's failed declaration mimics use experiment. I am absolutly against Int[N].

3 Likes

What about [Int : N] though? An integer generic argument might be another integer generic parameter (or in the future, a constant expression of some kind perhaps), and not just a literal constant.

4 Likes

Respectfully, consider me confused how that doesn't conflict directly with your previous comment:

If in the future we want to advocate for bare meta types again, [Int : N] would immediately be a counter point for "ambiguity". If that passes and there's advocates for allowing extending meta types for Hashable conformance (for whatever reason), that wouldn't be a possibility altogether due to ambiguity.

“Count should be on the left” - very good point.

How about 5[Int] or [5]Int?
Would that be ambiguous?


Edit: the latter variant would combine more easy in multidimensional case:

5[6[7[Int]]] vs [5][6][7]Int

vs proposed: [5 x [6 x [7 x Int]]]


Edit2: Some examples:

let x: [5]Int = [1, 2, 3, 4, 5]
let x: [_]Int = [1, 2, 3, 4, 5] // count inferred
let x: [5]_ = [1, 2, 3, 4, 5] // type inferred
let x: [_]_ = [1, 2, 3, 4, 5] // both type and count inferred
let x: []Int // potentially a new name for [Int]

Edit3: Applying this notation to dictionary types:

var x: [String: [Int: [Bool: String]]] = ...
var y: [String][Int][Bool]String = ...
y["key"]![42]![true] = "value"
1 Like

The way it works today is that the .self requirement on metatypes is artificial except when you have a generic type, like G<Int>.self, where it’s needed to distinguish it from an operator expression. The decision to form metatypes happens in a pass called “pre-checking” and its independent of the presence of .self. The rules are a bit strange but defensible, for example [Int] just always means an array metatype, while () and thus ().self is an empty tuple value.

If we decide to drop .self, we would need to resolve the parsing ambiguity for generic types, or model it as a new kind of overload. We could also reconsider the metatype formation rules so that for example () is either an empty tuple value or a tuple metatype based on context. Another possibility is that we can keep the .self but take it into account for this overload.

So any new type sugar has to parse as an expression too, and then translate into a metatype value under some set of rules. If we decide to add new metatype formation rules, it would still be wise to avoid new ambiguities because again, today the formation rules are independent of .self being there or not.

8 Likes

The former would, no? (with bare meta type future)

extension Int {
    subscript<S>(type: S) // ...
}

Thank you for explaining the technicalities.

To clarify my whole point: Should we be taking into consideration the future of bare meta types? The Dictionary-like-but-differs-by-a-space [Int : N] would, arguably, make that possibility a lot more difficult or remove it altogether.

It also adds additional rules and cognitive complexity while reading, trying to remember to discern with the presence of an individual space.

It is not just future—it is also past and present (SE-0090, reviewed 2016, deferred from Swift 3, returned for revision).

5 Likes

One thing I haven't really seen talked about is that, while syntaxes like [5 x Int] are clearly inspired by multiplication, multiplication is commutative and this x is not. To someone who isn't very familiar with the reasoning for the order (or maybe even someone who is), it won't be hard to forget whether [5 x 99] is to be read as "5 instances of 99" or "5 repeated 99 times". If we do plan to have sugar for init(repeating:), a keyword like of is much more clear than a symbol like x or *, and then of course the sugar for the type should match that.

3 Likes

Why is this allowed?

1.self
1.self.self
…

With all those being equal? It’s quite confusing.
I could see how we got that to be able disambiguating “the type itself, not it’s value), but it is still confusing and doesn’t compose well.

This system would be better imho:

1 // value of type Int
1.type // type Int
1.type.type // type of type Int
1.type.type.type // type of type of type Int
…

[1] // value of type array of value of type Int
[1].type // type array of value of type Int
[Int] // value of type array of type Int
[Int].type // type array of type Int
[Int.type] // value of type array of type Int
[Int.type].type // type array of type Int

with each .type consistently going up the “value-type-meta type-…” hierarchy.

1 Like

I think InlineArray will frequently be used in Embedded Swift because it doesn't need dynamic memory allocation. I already want a sugar syntax for it when I'm writing a bare metal program in Swift.

2 Likes

Further, while the operator clearly does not commute, it could be defined (in a future direction) to be right associative, i.e. [2 of 3 of 4 of Int] could be allowed to mean the same as [2 of [3 of [4 of Int]]].

2 Likes