[Second review] SE-0483: `InlineArray` Type Sugar

So when designing my own data structures I will likely use a short hand for deciding what kind of backing storage I want to use. Is the collection the main logical unit or are the individual objects the main logical unit (and they happen to be bucketed together).

So for example:

A video Frame is made of Pixels but I'm interested in them as a unit really, there are A LOT of pixels, and the count never changes. I'd rather manipulate them in place. My backing storage would likely be an InlineArray. For myself it can depend on a few factors whether I'd store graphics data in a matrices or use some other form of accessor math, but using a Matrix is common when working with dimensional data of lots of sorts. I suspect this will be a common use case. And as you've noted, wanting some kind of flood fill to initialize is also super super common.

An AddressBook is made of People and its good to have them collected, but I'm most interested in working with them individually and there likely won't be billions of them. Also, it's possible I'd have some complex storage issues if I'm trying to store an array of favorite Recipes on the type or something like that. I'd probably choose something else to back it. There might be a fixed number (like a PlayersOnField), but still right now Array works great and I can write my own checks for the count. There might be some batch processing to do, but it doesn't have to be at the sub 300ms level that people start to pick up on glitchy behavior in quite the same way something handling visual are audio output have to be.

Each type is exactly right for its own use case.

Initializing with one value across all comers is probably more common for InlineArray cases than Array cases so I really do see the argument for having [count of typeIntstance] produce an InlineArray by default.

But the syntax count of typeIntstance is just too catchy for people not to want to use it lots of places, and folks who haven't spent quite as much time as I have gazing at their navel about storage types will be surprised that two things that feel the same ( [1, 1, 1, 1, 1] and [5 of 1]) don't end up being the same.

I'm projecting here a little, but you might be noticing a drop in people who are going to use this type participating... because they just want the type already. There has been so much ... vigorous discussion... of names and sugar and whatnot. Continued discussion just feels like delay. A little bit of: "Do what you're going to do already. Let's just go"

1 Like

I don't understand the argument that the value sugar shouldn't default to InlineArray because it should be usable for Array. All existing literals have a default inferred type, if you need to use the sugar for a non-default type you can just specify the type. Extending this to Array is as simple as:

var array: [_] = [6 of 0]
1 Like

Wouldn't this compile and evaluate to true though?

EDIT: I mean, wouldn’t the equality evaluate to true.

I mean the right hand side is an inline array of Int, and i thought the left hand side was proposed as valid literal syntax for that as well

So it possible you actually meant that you just disagree that it is a problem, in which case sorry for the over explain.

It's not just "because it should be usable to Array", but also "what will it mean to someone not familiar with the existence of InlineArray".

Imagine that Array remains the intro type, a person walking up to Swift code who does not know about InlineArray's and its idiosyncrasies might see the following:

var array = [5 of 0]

and assume it is equivalent to

var array = [0, 0, 0, 0, 0]

Because they look kind of the same ish. "Both 5 zeros, 6 vs half dozen, cool"

But this pitch is that they would not be the same AT ALL. Those are two different objects. One is Array<Int> the other is an InlineArray<5, Int>

Admittedly it would make no sense for the Type Sugar to be [5 of Int] and have the Literal Sugar not be [5 of 0], but having that be the sugar-pair makes use of keyword confusing in any other context than the initialization of an InlineArray because of would have to belong to InlineArrays to prevent confusion really. Folks would have to get used to its presnse implying Inlineness. Which might be acceptable. TBD.

It means that allowing var array:[Int] = [5 of 0] would be allowing an Array<Int> to be initialized by a InlineArray<5, Int> literal. Which the language could of course decide to allow.

if 5 of 0 will ever == 0,0,0,0,0 in any contexts there is a problem with InlineArray using that shorthand as it's type sugar... to me (like what does it mean it the context of a tuple insert then? in the print example...)

2 Likes

Based on this pitch, my understanding is

[1, 1, 1, 1, 1] != [5 of 1] //true
[1, 1, 1, 1, 1] == [5 of 1] //false

corrected: I would prefer that to be reversed because them not being the same seems like it would be counter intuitive to someone not fully at one with the ramifications of different memory layouts for their collection types.

ETA: meanwhile if the hypothetical musing in other pitch convos came to pass...

(1, 1, 1, 1, 1) == (5 of 1) // would be true
2 Likes

Note: this proposal does not contain any feature as to values.

No matter what the default literal type would be for [5 of 1], which is the design question you're debating (probably best left for a separate thread since it is not under review, incidentally), there can be only two behaviors for the expression [1, 1, 1, 1, 1] == [5 of 1]:

  • Either it compiles and evaluates to true, because == compares two values of the same type with the same elements;
  • or it fails to compile because [5 of 1] is not valid syntax (as is the case today), or because it cannot be used to initialize a value of the same type as the left-hand side (if we choose to exclude this syntax from ever being used to express 'regular' arrays).
4 Likes

It is in the future directions, though (that [1, 1, 1, 1, 1] =/= [5 of 1]). Are those not part of the pitch?

// type inferred to be [5 of Int]
let fiveInts = [5 of 99]

// type inferred to be [5 of [5 of Int]]
let fiveByFive = [5 of [5 of 99]]

The trajectory of the pavement being laid matters?

But I feel like I've said my peace. Happy to drop it and enjoy WWDC this week! Hope everyone else does too.

No, future directions are not part of the pitch, unless you have a good argument that leaving it for a future direction is a mistake. For a future direction to make it into the language, it has to be pitched and reviewed separately exactly like anything else. It's just in the proposal to describe its relationship with what is being proposed.

In this case, there are elements of that future direction which are very relevant for the design of the proposal — we are designing this as if we will use the same basic syntax for both, and for good reason — but I don't think the type defaulting rules for the value literal syntax are part of that.

6 Likes

I am so sorry, but it is not a line of thinking, it is in-fact what it means. Keep in mind that this has not yet been formally proposed to be used in Tuples (which would likely require ()), in which case it would still meant he same thing. :-)

If it is proposed to use it in Tuples, then the following:

var tuple: (5 of Int) // (Int, Int, Int, Int, Int) i.e A tuple containing 5 elements of type Int. 

Compare to

var tuple: [5 of Int] // i.e an InlineArray containing 5 elements of type Int. 

I made an argument in the previous thread to support bare count of Type (no brackets and no parentheses), but it was rightfully pointed out that it then could not easily be applied to tuples if it were ever added.

Again, in all context it would still mean a Number elements of type Type despite what container type it is, (tuple or InlineArray)

6 Likes

The naked n of T syntax is not being proposed however. The type (and future literal?) syntax includes the brackets as well [n of T], which can be read as "an array with n elements of type T, no more no fewer," which in Swift is implemented with the InlineArray<n, T> type. Just as the [KeyType: ValueType] declares a dictionary, without the naked KeyType: ValueType having meaning by itself, i.e., this is not valid: var keyValue: Int: String.

Even in a future literal syntax, the of form will most likely only be part of a bracketed expression, e.g., let myArray = [10 of 0] and perhaps more complex expressions: let myArray = [1, 9 of 0], where it is still part of the literal syntax, not something used as a general expansion: fiveArgFunction(5 of 0). Maybe useful in its own right, but not proposed.

2 Likes

+1. I really like the proposal in its latest form using "of". "x" was controversial due to its resemblance to an operator (whilst not being one), whereas "of" aligns well with Swift’s tradition of other short keywords like "as", "is", and "in". Hope to use this soon.

6 Likes

I don't think it's correct to observe a specific characteristic of two sampled syntax constructs and backwards-extrapolate an explicit design rule out of it, especially since you have to paper over the fact that [K: V] isn't the same spelling as [(k: K, v: V)] to make it fit into that box.

After all, I can make a consistency argument if I slightly change the wording: [] represents a (lowercase-c) collection, and what's inside the brackets describes what is inside that collection. In the case of [Int], it's an unbounded collection of Ints, and in the case of [5 of Int], it's a collection of exactly 5 Ints.

This falls apart because it inverts the ordering of the dimensions for multidimensional arrays. It's easier to see if you make the dimensions different:

[Int]<5>  // InlineArray<5, Int>
[[Int]<5>]<10>  // InlineArray<10, InlineArray<5, Int>>
7 Likes

While I'm sympathetic to the idea that only elements should be in the basket, what you're writing looks even more like an Array with a fixed size taped to it than what we currently have (and I think what we currently have looks an awful lot like that already).

2 Likes

This is EXACTLY right. I made an argument to the contrary in the previous thread and I 100% took it back for the simple fact that I do want count of Type to be expanded to Tuples.

I wish there were more bracket types in ASCII (or rather just on typical keyboards) so we could use other brackets for other types.

I also wanted to point out that I am OK with making inline arrays require some other syntax as well since there is some pushback saying that [T] should be exclusive to current Array.

For example we would make "#[T]" for InlineArray or "@[T]" for NSArray. It is just an idea that I have no ties to.

3 Likes

+1

I wasn't convinced before, but I've come around on the idea of having sugar for declaring this type. And I like the of spelling in this revised proposal.

I still have some concerns regarding the literal syntax mentioned in the future directions, but that's not being proposed here so I'll save that for a future discussion. (My primary concern is what default type should be inferred for the literal.)

I am new here. This is an improvement over x, but I do not think [5 of Int] is conceptually sound. As some have noticed, [5 of Int] is confusing as it does not suggest inline, nor not-a-collection.

I suggest a better syntax by drop the [] entirely. This makes 5 of Int an InlineArray<5,Int>, which looks intuitive to me.

This enables things like (5 of Int, 3 of Bool) and 3 of 5 of Int. This also means that the current syntax of [5 of Int] would mean [InlineArray<5,Int>], which looks correct and intuitive to me.

To aid in parsing, parentheses could be required in the top level type name, like (5 of Int). But it would really mean a one element tuple of InlineArray<5,Int>, which is just InlineArray<5,Int> itself.

Edit: I just found out the proposal mentioned about this approach briefly at the end.

4 Likes

InlineArray is definitely a collection, it's just not a Collection.

3 Likes

(I've emphasised the second bullet point of the review 'format', as that is my main point)

What is your evaluation of the proposal?
-1. I like of better than x, but my feedback isn't about the quality of the proposed feature per se, but more about the necessity of it.

Is the problem being addressed significant enough to warrant a change to Swift?
Not at this time.
In particular this review calls for use cases for this feature. Here is how I expect to be using this feature and why having sugar syntax is not required for me at this time.

  1. When I have the need for a collection (as in its behaviour) I typically create one of the 'vanilla' collections as a starting point (Array/Dictionary) depending on how I want to interact with the collection
  2. Wrap the collection in its own type (Object Calisthenics rule #4)
  3. Only based on performance requirements and after profiling would I opt for more specialised collection types. In other words, only when I have a performance problem, CoW seems to be the culprit and inline storage might provide a solution, would I refactor my type to use an InlineArray instead of the regular Array.

Note that by that time the array type used has become an implementation detail. And writing the full type annotation is negligible in impact in the grander scheme of things.

Does this proposal fit well with the feel and direction of Swift?
I don't have any comments on this.

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
I've used other C type languages that use fixed size arrays. Typically they have a concise way of describing variables of these types. In that sense you could say that supports the need for sugar here. However, I should also note that in those languages (C/C++/C#) the fixed size array is typically the 'default array'.

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?
I participated in the pitch, previous review and read through all the comments.

4 Likes

+1. It is much better than x which suffered from being neither an operator nor a sensible keyword. of is clearly a keyword and the best one available given the limitations of English.

Other languages have words for this. Chinese e.g. uses classifiers like 个 which go between the number and the noun. In English though we indicate plurals with a plural noun. Usually by appending "s" but some noun plurals are irregular. "three cars", "three sheep", "three women".

But some nouns do not have a plural, and for those English uses "of": "three of me", "three of those", And this is similar; it makes no sense to write [3 Ints], as appending s to Int makes it a different unrelated type. Without that available of makes sense.

9 Likes

I'd like to second this. The brackets are unnecessary and make the type feel like it has a level of indirection (like Array has), whereas 5 of Int without brackets feels like you're just stuffing 5 Ints in place, thus inline. At least, that's how I see it.

No brackets also means the "Flattened multi-dimensional arrays" future direction is already fulfilled: 5 of 5 of Int works naturally if brackets are not part of the syntax; no special exception needed.

4 Likes