SE-0483: `InlineArray` Literal Syntax

Has some kind of partial generic been considered?

If [Int] means Array<Int> then [Int]<5> can mean InlineArray<5, Int>.

The by keyword could be used to sugar multi-dimension:

let fiveByFive: [Int]<5 by 5>

or just , if we don't want to introduce a keyword:

let fiveByFive: [Int]<5, 5>
6 Likes

This can even be kind of be done already using typealias:

typealias InlineIntArray<let count: Int> = InlineArray<count, Int>

let elements: InlineIntArray<4> = [1, 2, 3, 4]
4 Likes

The more I read about this, the more I question the necessity of it all. It's fun to suggest competing "best syntaxes" for this... but do we need it? I think the demonstration there's a need is pretty weak.

The motivation for this proposal explains it's more cumbersome to write InlineArray<5, Int> as compared to [Int] for a dynamic array. I think it fails to demonstrate why that's a problem.

Then it shows that it's even more cumbersome when nested. Okay, but how often are people going want to nest them like that instead of reaching for, say, a Matrix type?

The initial idea of calling this type Vector has been scrapped to avoid overloading a storage type with maths operation, with the idea that domain-specific types built on top of would be more appropriate. I'd say the same apply to nested inline arrays: they should mostly be used as storage within other types.


If the problem is that InlineArray is too verbose, I think the following "sugar" would be perfectly fine:

typealias Inline = InlineArray

var x: Inline<3, Int>

A short name could be all that's needed to solve the problem of… the cumbersomeness of the name that was just chosen?


And if for some reason we really really need sugar, I'd reiterate my suggestion of dropping the brackets, just writing 5 of Int. No brackets feels more inline, and that's what this type should emphasize.

17 Likes

I also have landed pretty firmly in the “No need for sugar at the present time” camp.

I agree with this. I would very much expect, for example, a graphics library to define things like this:

struct Vector<let count: Int, Element> { ... }

extension Vector where Element: Numeric { ... }

typealias Vec4<Element> = Vector<4, Element>

The Vector type would wrap an InlineArray property, which only gets declared once so any sugar is irrelevant. Then all usage sites would traffic in, eg. Vec4<Float>.

• • •

One thing that is important to have good sugar for, is initializing a Vec4 from an array literal. Specifically, it should be a compile-time error to provide a literal with the wrong number of elements:

let u: Vec4 = [1, 2, 3]        // error: too few elements
let v: Vec4 = [1, 2, 3, 4]     // good
let w: Vec4 = [1, 2, 3, 4, 5]  // error: too many elements

The original InlineArray proposal SE–453 talks about this in the Future Directions section, and I think we need to have a good solution here before we can consider InlineArray to be “fully baked” and ready for widespread use.

• • •

Once we have that available, so people can make types that are “expressible by fixed-length array literal”, then we will be able to see how programmers actually use InlineArray in practice—which I expect will often involve a thin wrapper type to provide domain-specific functionality such as vector arithmetic.

When that happens, then after some time has passed we can revisit the current proposal in light of the collective knowledge that the Swift community has gained from real-world experience using InlineArray with domain-specific wrapper types.

In summary, I believe that SE–483 should be deferred until a future date, once sufficient time has passed after the introduction of the ability to express custom types by fixed-length array literals.

19 Likes

I like this design a lot because it doesn’t introduce any new operators or operator-like characters, and feels like a natural extension of the existing Array type syntax.

3 Likes

One could argue that [n; Int] is similar to [n Int] syntax, except it doesn't rely on white space, which is a plus for some.

I doubt one would confuse [number; Type] pair with [Type1: Type2]. Once one gets used to it doesn't cause confusion. It's not like people confuse : before type with dictionary entry or vice versa.

Wouldn't having generic and non-generic overload for same typealias ([typealias [Int] = Array<Int>) confuse the type checker?
It it even possible to have generic and non-generic overload for the same typename?

A question to ask is whether InlineArray literals should have same form as Array literals, namely [...].
For example, one should be able to initialize a 2x3 matrix by something like

let myMatrix: Matrix = [ [1, 2, 3],
                         [4, 5, 6]]

The question is what would make the compiler treat the right side as an InlineArray expression and not as an Array expression.

The InlineArray expression part requires more attention I believe, when introducing typealias for InlineArray. Will allowing array literals to be inferred as InlineArray

  1. Guarantee the proper dimensions at compile time as @Nevin suggeted? It is a must.
  2. Result in even more cases of error: the compiler is unable to type-check this expression in reasonable time? It is something that has to be tested as early as possible and if it results in unreasonable compile times - avoided.

Now, if the conclusion is that reusing array literals is not optimal, it is a case for ; syntax (or another rarely used symbol). Having something like [; ... ] for InlineLiteral expressions would not make type checker sweat more, and while ; is a bit "ugly" it doesn't introduce much noise.
A better option, I believe would be introducing different braces, for example [| |] instead of [ ] (feel free to replace [| |] with anything that feels more convenient). It would both signal that these are different entities from regular arrays, and also make introducing the size argument trivial - one would write [| Int |]<5> without being ambiguous. Again, [| is just a place holder, there are probably better options.

PS: If the fears of resuing [ ... ] syntax for InlineArray expressions are not justified, and it can be demonstrated that size will always be known at compile time and compile times will not be affected - then [_ x _] syntax is good enough.

I've seen this sentiment from a number of folks, that we should "wait and see" whether we need the syntactic sugar and add it later.

I don't think that approach is a good one for the Swift community. If the end state is that we will have type sugar for InlineArray, then the community benefits from have that type sugar introduced at the same time as InlineArray. That way, all of the documentation, tutorials, talks, style guides, examples, and so on can take the type sugar into account. Developers can learn its meaning once and use it going forward. There's a cost to making changes later on, even if it's as simple as adding new sugar, because you've invalidated some of the existing body of knowledge.

We shouldn't delay the introduction of type sugar just because we're afraid to make a decision now. This is a highly-requested feature, and has been for years: it's going to get used. The uses we envision today aren't going to be largely different from the uses tomorrow.

Let's find the best solution for this type sugar---or no solution, if this problem isn't big enough to solve---so we can introduce and teach it once.

Doug

27 Likes

A post was merged into an existing topic: Why did the InlineArray type sugar proposal come to review?

Notice how your sentence begins with the word “if”.

It is not a foregone conclusion that we will actually want any sugar for this type. We do not yet know how common it will be to use InlineArray directly.

One of the main reasons for changing the name of the type from Vector to InlineArray was because this type is not suitable for direct use in many common scenarios.

It is fully expected and anticipated that programmers will often use a wrapper type around InlineArray, rather than using InlineArray itself. If that does indeed pan out, then it is entirely reasonable to question whether any sugar is needed, wanted, or desired.

Since both possible futures are predicated on an “if”, and it is not at all clear which of them will come to pass, it therefore follows that waiting to see how things play out before committing to a decision is an entirely valid position to hold.

11 Likes

I did not mean to guess at motivation. Postponing the introduction of type sugar is noted here, here, here, here, and here. As I said before, I think postponing a decision is worse than accepting this proposal (or something similar to it) or rejecting this proposal. Let's introduce and teach this feature once.

5 Likes

The flip side of this argument is that sugar is an extra thing to learn about. By definition sugar is non-essential: it should never be gratuitous but at best it is a nice thing to have in certain circumstances. Introducing a complex new feature and sugar—with completely novel syntax!—for the new feature means there is more to grasp up front before one can master the subject.

By contrast, the feature can be introduced, allowed to become familiar, and then later helpers (sugar) added. With a good grasp of the fundamentals of the feature, grafting a new convenience onto one's understanding is no particular hardship. In fact it's likely to be taken as a welcome bonus. Crucially, adding the sugar does nothing to invalidate one's existing knowledge. It doesn't make old writeups of the feature wrong or less useful.

If that bonus is required up front; if the sugar is in some way necessary for the feature to be usable, then it's really not sugar at all and we shouldn't be describing it that way.

15 Likes

Yes, you will find that I agree with you in my post ;) But, I do think the discussion of whether the problem we are looking to solve is one of the types or literals is quite important, because I think the choices we make actually influence how annoying using InlineArray is. For example, I think this is pretty reasonable:

let array = Array(repeating: 99, count: 5)
let inlineArray = InlineArray(repeating: 99, count: 5)

(I will admit that I haven't downloaded the toolchain to see if this works, but if it doesn't, it probably should.) If we add a new literal syntax, it seems pretty straightforward to extend it:

// Obvious placeholder syntax
let array = [99 repeated 5 times]
let inlineArray = [99 repeated 5 times] as InlineArray

But, this begs the question: is this the only problem we are looking to solve? I would lean towards "yes" but maybe we have people who are writing func rotate(transformation: InlineArray<InlineArray<Float, 4>, 4>) a lot. Or maybe they are declaring let path: InlineArray<CChar, PATH_MAX> members. My point is that I want to see these examples. and then rate them in context for whether I think sugar is warranted. I want to be able to say "I think it's likely that these would be typealias TranformationMatrix and typealias PathBuffer" or maybe going "Oh, you're right. This is actually something that seems like it would come up a lot". Show us real code where the type is being spelled out, and let the community decide whether they agree that it looks like it deserves sugar. And if it does, whether they agree with the specific sugar you've chosen for it.

No, let's not do that. This has historically not been the case and I don't see any reason for it to be an overriding motivation now. Swift has a lot of features which have gotten syntax sugar for them after the fact and I don't think this has ever been a significant problem. SE-0185 automatically synthesized Equatable for simple structs and the Swift community very quickly figured it out because it made their own lives easier. The world didn't end because we approved SE-0345 (if let shorthand). It just means that people cleaned up code as they went along and new code ended up nicer.

We can and we should defer syntax sugar decisions if we don't have enough information on how this is being used in the real world. It's not hard to update the documentation later. This argument holds very little water, and doubly so because I feel that most Swift developers have no need to use this type anyway. I strongly suspect the average style guide's mention of InlineArray will be "don't–use Array instead" rather than "here is how you should write it out" .

21 Likes

Is it improper to produce a counter pitch?

This is less about this proposal than about SE-0453 but the repeating initializer for InlineArray doesn't take a count. This does necessitate some fiddliness (when you don't have type context):

let inlineArray = InlineArray<5, _>(repeating: 99)
2 Likes

I understand this discussion is not about value literals, but has it already been decided that it is going be the same as regular array, namely [...]? If there is still room for discussion about syntactic sugar for InlineArray literals, may be it should be done in parallel or before deciding the proper sugar for the type name - since they need to mimic each other somehow, like Dictionary and Array do.
If it has already been decided that [...] is the syntax for both Array and InlineArray literals (because SE-0453 and this proposal suggest so), has the potential "stress" it puts on the type checker been taken into consideration and tested in various scenarios (complicated expressions, lambdas, result builders, function/operator overloads etc)? Because if not - there's a big chance that it will break existing source code too - there is a possibility that׊non negligible amount of valid and idiomatic existing code that uses arrays might suddenly stop compiling because of error: the compiler is unable to type-check this expression in reasonable time.

Let's be honest, type inference is sort of "weak link" in Swift compiler, often breaking when compiling trivial code and failing to produce informative errors while doing so. So if new literals are introduced, a care needs to be taken to make sure it doesn't make type checker break more often.

But, the presence of brackets strongly indicates an array.

3 of Int
3 of 3 of 3 of Int

Without the brackets, those integers could be interpreted as forming an array, a set, or a tuple. Which one?

In all programming languages I am familiar with, [...] is used with array declarations.

Skipping C and C++

limbo

arr : array of int;
arr := array[10] of int;
arr := array of {1, 2, 3, 4, 5};

go

var arr [5]int
arr := [5]int {1, 2, 3, 4, 5}
arr := [...]int {1, 2, 3}

var arr [rows][cols]int
var arr [3][4]int
2 Likes

It seems to be common use, but it doesn't mean they won't be used at places regular arrays are used right now. Literally, every time when we have a immutable list of values, or a list of values that is not likely to change it's size, it is a good use case for InlineArray.

Why? Literally every time one has a list of values that is not supposed to change, using InlineArray would make more sense than Array. Especially if these are not supposed to be copied a lot, or small enough (which they often tend to be) to make the copying cost negligible. And there are many other potential use cases.
Array is expected to be more common because of:

  1. Inertia - since it has been around much longer.
  2. Not up to date documentation - I expect the official documentation and "up to date teaching guides/tutorials" take a few years, before they update their examples to use InlineArray when appropriate, like it happened with the most of features introduced in last few years. (~Copyable, proper description of concurrency, macros, etc).
1 Like

Recognizing the sugar would be the job of the parser, not the type checker. The type checker would not be put under any more "stress" than if InlineArray were written out explicitly.

1 Like