SE-0483: `InlineArray` Literal Syntax

Being reminded of matrix_float4x4 has softened me quite a bit, but I still don't like any of the n x T variations, preferring of instead.

1 Like

Then, the compiler must allow [1024 個の UInt8] or [UInt8 が 1024ヶ] clearly when your locale is ja_JP.

Joking aside, we'd better consider people who write programs in Swift have various cultural/linguistic backgrounds, even though Swift syntax is definitely based on English.
In my country, for example, "1024 instances of UInt8" is more often represented by UInt8 × 1024 rather than 1024 × UInt8.
Thus, [5 x 99] (only described in "Future Directions" though) will be interpreted as "99 integers whose values are all 5" in my country.
I think any operators (e.g. x, ×, or *) themselves have essentially universal meanings, but word orders don't; no matter which operator is chosen.

In that sense, I want to support of keyword (if sugar is necessary) because of is evidently an English word.
I mean, when we see 1024 of UInt8, we will parse it in English word order because we can easily recognize it's based on English.

That's somewhat ironic, but we should stick to English words here if we value diversity.

14 Likes

You’re right. I’ve actually made the mistake several times now reversing the proposed meaning of x as described in the current proposal.

I never thought that I would be so easily overcome by that mistake. By user example, mine, I can attest to it’s confusing nature, lending more evidence to myself for why of is superior.

12 Likes

I believe it would be a mistake for us to accept "x" as proposed without considering the implications on the future directions to find ourselves in a situation we don't quite like the outcome:

[1 + 2 x 3 + 4]    // 🤔"that's [11], right?"
7 Likes

I don't think this would be advantageous to have in the language. Tuples are not well-suited for storing large amounts of data, so a syntax making that easier feels undesirable.

In my experience, I rarely need to write comma-separated lists with repeated values and non-repeated values interspersed with each other, so I don’t think I'd find this syntax much more useful than a repetition-only syntax.

If this is something we want to sugar, I feel it should be done with the repeat keyword, since that’s the keyword variadic generics uses for splatting.

I don't see why we should reuse the InlineArray syntax for non-empty Arrays. [some of some T] would still be possible if inline arrays used x.

1 Like

I just want to express my support for introducing the syntactic “sugar” for InlineArray now, rather than delaying the decision. A strong point has already been raised in this thread: there haven’t been any new syntax ideas in the past decade. I agree — it seems unlikely that something better will come along, and waiting might only postpone a solid starting point for adopting InlineArray.

While I’m not entirely sold on x, it does suggest a kind of cartesian product to me when combined with a type, and it reads well in type definitions, especially for multidimensional cases. I’m less convinced about how it might extend to future literal syntax — but that part still seems open. I’m also not particularly fond of of.

Could someone clarify what exactly is meant by “keyboard-accessible”? For example, would the following be ruled out?

var adjList: [5 | [9 | Int]] = [5 | [9 | 0 ]]

var adjList: [5 ° [9 ° Int]] = [5 ° [9 ° 0 ]]

Thank you for that. Could you tell me how you plan on using InlineArray in general. I think it would be helpful to understand the community’s use cases.

Currently a lot of C is imported as Tuples, and many of those declarations are repeated Types in various projects. So for the declaration site, they absolutely make sense.

Some other use cases.

  1. Tile Maps

let tileMap = [5 of 0, 3 of 1, 4 of 2] grass, then water, then stone

  1. Arduino singlas

let signal = [5 of HIGH, 5 of LOW, 2, 2, 3 of 10, 1]

  1. Programming a string of lights.

let lights = [10 of [255, 0, 0], 10 of [255, 255, 255]], 10 of [0, 0, 255]

And if it were required because the API was imported as tuples..

let lights = (10 of (255, 0, 0), 10 of (255, 255, 255)], 10 of (0, 0, 255))

I am certain the community can do better than me at coming up with examples. I don't do embedded style programing often. I do recall that there are some setup signals that often require repeating, I just can't think of it off the top of my head.

1 Like

Tuples as they exist today are not suited to this use case for two reasons: 1) Each element is independently type checked, meaning that type checking gets slower with each added element. 2) The last element of a tuple isn't padded to it's alignment, which means it's not safe to pass homogeneous tuples to functions expecting to copy around C arrays.

The first would not apply to an (n x T) tuple as it would obviously not need to infer the type of every element, and the later would not apply here as (n x T) is being suggested as sugar for InlineArray instead of tuple.

In the post I was replying to, (5 x Int) was meant to be syntax sugar for a tuple, with [5 x Int] being sugar for InlineArray.

Another big reason tuples are ill-suited for storing a lot of instances is that Swift's calling convention always destructures tuples. This means that if you pass a tuple of 1024 UInt8s to a function, that's equivalent to passing 1024 UInt8 parameters to it. This causes massive amounts of copying and a huge increase in code size. An inline array of 1024 UInt8s, on the other hand, is passed indirectly (by pointer), eliminating the need to make a copy of each element just to pass the array to the function.

Is that the case? I was under the impression that a caveat of using InlineArray is that the contents are copied when passed as a parameter so one has to be extra careful.

Example

The code

@inline(never)
func sum<let N: Int>(_ array: borrowing InlineArray<N, UInt8>) -> UInt8 {
    var result: UInt8 = 0
    for i in 0..<array.count {
        result &+= array[i]
    }
    return result
}
let randVal = UInt8.random(in: 0...99)
var arr = InlineArray<1024, UInt8>(repeating: randVal)
let _ = sum(arr)

generates (Compiler Explorer)

generic specialization <1024> of output.sum<let A>(Swift.InlineArray<A, Swift.UInt8>) -> Swift.UInt8:
        sub     rsp, 1032
        mov     rsi, rdi
        lea     rdi, [rsp + 8]
        mov     edx, 1024
        call    memcpy@PLT
        pxor    xmm0, xmm0
        mov     eax, 112
        ...
        add     rsp, 1032
        ret

Notice the sub and add of rsp and the call to memcpy.

4 Likes

I have not seen it mentioned in this thread, but x is also used in hex literals, which negatively impacts readability:

[0x10] // [Int]
[1 x 10] // InlineArray<1, Int>

I think the value sugar case tips the scales in favor of of over x for me.


Separator option not mentioned in the proposal:
In music notation, |: :| is used to denote repetition (written in ascii), so perhaps

[5 |: Int]

could be an option if we're inventing symbols for n repetitions.

22 Likes

In Swift, arguments larger than a certain size (4 words on most platforms, 3 words on i386) are always passed via pointer. I don't believe there's a need to copy the value before passing it via pointer, since Swift requires exclusive access to memory for mutation.

I don't believe this copy is required by Swift's calling convention. I think it may be a bug — this copy doesn't seem to be necessary, and avoiding unnecessary copies seems to be a goal for this type. Someone who works on the Swift compiler would probably know about Swift's calling convention and InlineArray's intended semantics better than I do.

Note that if you remove <let N: Int> and directly specify that array is an InlineArray<1024, UInt8>, then no copy occurs at all. (Compiler Explorer)

I’d much prefer a comma as delimiter.

var items: [5, Int] = …

The inferred case [_, _] looks familiar in swift

Why use borrowing here? I was under the impression that it's totally fine (from performance POV) to pass a large struct as a parameter as it would be passed via the pointer and no actual copy is done, is borrowing buying you anything?

Nice one. I'd like to add that while your example shows the confusion of using x in value literals, it's the same for such usage in type literals:

[0xA] // a value of [10]
[1 x A] // a type of InlineArray<1, A>

Also, start from your idea of musical notation, I'm wondering is it possible to reuse the existing keyword repeat from value/type packs

[5 repeat Int]
// or
[repeat 5 Int]
// or
(repeat 5 Int)
6 Likes

It doesn't make a difference here since arguments are borrowing by default for copyable types. I hope I'm wrong about the copying, and I hope that I can trust that InlineArrays are passed by address. The code generation though suggests otherwise.

If I remember it correctly, the default ownership syntax for a parameter of a normal function is implicitly borrowing already. Also, manually stating borrowing does not control how the value is passed at low level, the compiler will decide whether to pass an address or perform a "bit-wise borrowing".

Reference: Doesn't Swift borrow immutable structs by default? - #7 by Joe_Groff

+1 on the proposal from me.

I didn’t plan to comment on this review. But since the discussion has been quite long, somewhat heated, and in my opinion surprisingly negative against the proposal, I just wanted to show, that some of us actually like the proposed changes.

I have read the proposal in detail as well as most of the pitches and threads leading up to the proposal.

When it comes to syntactic sugaring, personal preference seems to be influenced more by differences in taste and experiences with prior art, than by logic and verifiable facts.

From my point of view, the proposed usage of x as a contextual keyword when declaring an InlineArray<5, Int> feels natural in general and fits well into Swift. But I also (mostly) understand why some don’t agree. Using e.g. of as an alternative is okay, I guess, but doesn’t scale as well to the future directions with multiple dimensions like:


var bitmap: [1024 x 768 x RGB]

Looks much more natural to me than:


var bitmap: [1024 of 768 of RGB]

I personally don’t find the usage of x to be “too cute” or “unserious”. On the contrary, it’s a well established usage in many situations for specifying a number of items (x = times) or e.g. the dimension of an area. Especially when usage of a proper times/multiplication symbol is impractical (i.e. × or U+00D7).

Another debated aspect is the (possible) future literal sugar with the type inferred by the initialized default value:


var row = [32 x 17]

Is it 32 integer values initialized to 17, or the other way around? In seems too ambiguous for my taste. And I also suspect, in many practical situations, that the inferred type of Int is not “precise” enough and would preferably be expressed as e.g. UInt8 or Int32.

One suggestion to improve the clarity of the literal syntax is to require the “value” to be expressed using an initializer expression, possible spiced with a range operator:


var row = [32 x Int32(17)]

var buffer = [128 x UInt8(0)]

var digits = [10 x UInt8(48…)]

var blue = [1024 x 768 x RGB(0, 0, 255)]

var sound = [512 x Double(0.0)]

Not as short or pretty as the proposed literal, but clearer and easier to understand, if you ask me.

3 Likes

Interesting, how do you feel about x being used in various proposed contexts that would make wider use of the syntax?

See this post for examples.

In those contexts, using of is probably cleaner than x, but only slightly. It would still work okay with x.

But I’m not completely convinced, that extending the concept to that “extreme” is necessary or wise. It easily gets hard to read and understand, regardless of which keyword gets chosen.

At the same time, it’s a nice idea and feels neat for the reasonably complex cases. So I’m a bit torn.

Still I wouldn’t mind using x if this would be implemented in the end. When things get complicated use parentheses to clarify or change the name of the variable if it clashes with x.

Not ideal maybe, but in my view it’s more important to get the main usage as sugar for InlineArray and the literal sugar right, is more important than a possible generic extension to series of values.