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

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

2 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.

8 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

I understand as I used to make the same argument; however, this would kind of preclude us from being able to use () to construct repeating fields in a tuple. If we decided that we wanted to use that syntax..

let t: (3 of Int) // (Int, Int, Int)

Under your proposed meaning, that would just equal the same thing as bare 3 of Int. As tuples of one element don’t exist in Swift.

I would hate to see of confined.

If it is absolutely true that no other use of of will be used then ok. Bare of is fine.

+1 While I was never a fan of of, I'm willing to live with it. I fully support adding sugar for InlineArray. If we've decided that of is the keyword to use, then [n of T] is the correct sugar, and definitely generalizes to a future [n of x] literal syntax.

I'm also interested in a future (n of x) literal syntax for homogenous tuples, although I expect them to be less frequently used now that we have InlineArray.

An element of [(k: K, v: V)] is not K: V.
An element of [K: V] or KeyValuePairs<K, V> is K: V.
K: V is not just a type alias to a tuple.
The difference can't be represented in the type system. The only place it exists in Swift is in Dictionary type sugar, and ExpressibleByDictionaryLiteral values.

This would come more into play with of in a hypothetical InlineDictionary literal.

[5 of String: Int] // 5 "String: Int"s
[5 of (String, Int)] // 5 (String, Int)s

I could see it working as long as [? of Int] is added to the language and [Int] is redefined as shorthand for that. (I still think that's confusing, but you can't get rid of [Int] now.) [Int] looks like it ought to be shorthand for [1 of Int].

[1] as [1 of Int]
[1] as [Int]
[1, 2] as [2 of Int]
[1, 2] as [Int]
[1, 2] as [? of Int] // This is clearer, if we have to form a mental model for `of`.
<5>[Int]
<10>[<5>[Int]]

is better. In general (because most types are nouns), bracketed stuff reads more clearly over there on the left, so I don't know why everybody does it the hard way.

<Bool>Array
<Int>Set
<String: Any>Dictionary

[Int]<10, 5> was previously proposed. I think it's good, but doesn't scale past multidimensional InlineArrays.

If you're making an argument that [5 of Int] isn't consistent with other uses of [...], then I don't think inventing an entirely new form for generic argument clauses out of whole cloth for a specific sugared type where the arguments are written to the left instead of the right is a consistent or realistic option, either.

2 Likes

Imo that's not really a problem because in contrast to C, the nesting square brackets clearly indicate which is the inner and which is the outer arrays count.

1 Like

Nesting or otherwise, it would be a completely unforced error for readability and education to make [[T]<n>]<m> and InlineArray<m, InlineArray<n, T>> mean the same thing. The choice to position the dimension first in the nominal version was specifically made to ensure that the multidimensional cases read correctly.

3 Likes

As much as I would have liked InlineArray as a concept to be the same thing as a tuple, I don't think supporting the above syntax is a good idea. It encourages creating tuples with a large number of elements, which won't work very well ABI-wise in Swift (creating large symbol names).

The repeat keyword is already used to "expand" things in-place. Perhaps it's better to allow (repeat 3 of Int) to expand to (Int,Int,Int) and (3 of Int) on itself would be (InlineArray<3,Int>). This would further allow things like (repeat 3 of 3) to become (3,3,3).

The repeat keyword really sells that it is literally duplicating in-place.

2 Likes

Good point. I would be OK with limiting it to, say, 16 or some arbitrary arity for tuples if that were the case.

Personally, I don't think it's desirable to have a literal syntax for every type of array in the standard library. People would need to learn each syntax, even for array types that are rarely used, and types outside of the standard library wouldn't be able to make their own sugar.

Type nesting, on the other hand, doesn't require every type to be memorized and can be done by non-standard libraries. If we want to bring the conciseness benefits of sugar to other collections, I think nested types are a better path to go down than a bunch of literal syntaxes.

let a: [String: Int].Ordered = [1: "one", 2: "two"]
let b: [UIColor].Contiguous = [.red, .orange, .yellow]
let c: [Int].Small<5> = [0, 1, 2]
6 Likes

I'm a bit sad to see x go. My main issue with it was that it wouldn't compose well with a future value version (ie [5 x 3] being an InlineArray of 5 Ints, each of them equal to 3) as it was too easy to confuse with matrix notation (ie a 5x3 matrix), which did not seem like a solvable problem.

And yet, I can't shake the feeling that x was brilliant in a way of isn't. Don't take me wrong, [5 of Int] is not bad at conveying "a collection of 5 Ints", but it's strictly inferior to [5 x Int] at that.

I wonder, if in the quest of finding and discussing all the possible edge cases ([_ x x], [n x x]...) and ways of "holding it wrong", we may have lost sight that what this sugar fundamentally needs to accomplish is a shorthand to declare an array of a fixed number of items, and excel at that.

In any case, I think of is a good compromise given the unresolved issues of x for value sugar, and I'm looking forward to being able to use this. I do think syntax sugar is going to be immensely useful for those that need to reach for InlineArray.


Personally, I'm particularly sad to see the future direction for flattened multidimensional arrays now being [5 of 5 of Int], because [5 x 5 x Int] is much better than any other syntax for multidimensional arrays I've ever worked with, with unmatched clarity when translating from/to mathematical notation. In comparison, [5 of 5 of Int], or the unflattened [5 of [5 of Int]] are... underwhelming.

But oh well, that's still a future direction in any case.

6 Likes

If the alternative is InlineArray<5, Int> I find the proposed syntax to be much worse.

I don’t see any strong reason to introduce shorthand/sugar for this, other than to correct the „mistake“ (which I don’t really see as such) of privileging the plain square bracket syntax for Array. Others have rightly commented that InlineArray is not the only new type that may want syntax along these lines: why InlineArray (only)? I think it’s too soon to make the call if nothing else.

Overall it appears to be just adding more inconsistencies in the language for a minimal gain at best. This kind of addition seems to be increasing complexity in the language, which already has way too many moving parts and „hidden knowledge“ requirements.

In short: -1

16 Likes

+ 1

I feel that this sugar is necessary. While InlineArray is clearly going to be used sparingly in many places, I think that a lot of people are forgetting how frequently this will be used in performance-critical code and how this must completely replace Array on many embedded platforms that can’t allocate on the heap. In these environments, I don’t have the chance to try the “normal" collections and profile them as this is not possible. While I don’t think InlineArray will ever be the “default” in Swift, it’s clear that it will be used quite frequently.

I do agree that of makes more sense than x because, even though it is a bit longer, it more explicitly states what this actually means and fits better for the value sugar. I think that the [] around it works because it shows that this is a collection. Both the array and the dictionary sugar uses this, and it doesn't make sense to break this convention from many other languages without a very good reason that InlineArray should be treated differently. Using () doesn't make sense because while the memory layout is similar to tuples, the usage isn’t and we shouldn’t be encouraging people to think of InlineArray and tuples similarly.

I don’t like the [Int]<5> syntax because I think it throws away many conventions in Swift and other languages, it just isn’t very readable because it doesn’t explain what the number means, and it puts the size on the wrong side of the element type.

Personally, I think that the possible syntax for flattened, multi-dimensional arrays ([5 of 5 of 5 of Int]) is absolutely unreadable. If we want to use of, there needs to be some separator because it’s not an array with 5 of 5 of 5 of 
, it’s 5 of [an array with 5 of [an array with 
]]. I think we should either not add it at all, or there should be some other delimiter between elements of the shape and the element type:

[5,5,5 of Int]
// or
[(5, 5, 5) of Int]
5 Likes

I appreciate your point of view here. And I agree. Although I am not often writing optimized code, I have a lot of respect for those who NEED to be writing optimized code using these specialized, constrained types. When reading code that has been meticulously crafted, I am always surprised at the lengths programmers go to ensure speed, low memory usage et cetera. For those of you who are unfamiliar with what I am talking avout, a great starting point would be some of the discussions (or even source code notes) which debate the use of a tuple, struct, class, Copyable and ~Copyable and their performance nature (allocated on the stack vs not, memory footprint, copy performance), and the various ways to avoid certain non-optimized circumstances that are not explicitly exposed to the developer in the API -- parts of the language that you would have to have understanding of the internals to grasp a detailed understanding of.

Although I will likely not use InlineArray much (even in circumstances where I should), those who are confined to performance constraints deserve the attention we give here.

If any of you are heavily active in generating optimized code where you believe that this Sugar is NOT needed, please speak up. I would like to hear from you too.

A part of the discussion I really wanted to draw people's attention to was giving us an idea of how they would be using InlineArray, to help us fully understand THEIR needs. I imagine that Hamish Knight, Ben Cohen both understand this need which is why they are advocating for it.

1 Like

The syntax let a: 5 of Int could be read by someone not knowing the language as just saying “5 is of type Int”. This could be used as a guideline. The syntax let a: [5 of Int] indicates that there is more to it, and those brackets are common in other programming languages.

let a: [5 of Int] Is not logically correct, as 5 of Int is not the type of the elements, but natural languages are also not “logically correct” sometimes but function quite well. I think [5 of Int] is a nice syntax.

2 Likes

If it wasn't clear, I said the brackets should go on the left of everything, not just <5>[Int]. If it can only be done with integer generic parameters, at this point, so long after the wrong decision was made, that's still really good.

<10>InlineSet<String>
<4, 4>Matrix<<16>Float>