SE-0483: `InlineArray` Literal Syntax

IIUC @Ben_Cohen's point was that when you conceptualize InlineArray as a block of memory, you instead read the size of the element type: [5 x Int] is a "5 by 64 bit" block of memory.

I really don’t like the change of syntax for specific types.
What’s wrong with
‘’’swift

let fiveIntegers: InlineArray = .init(repeating: 99, count: 5)

‘’’

Wouldn’t that be more consistent?

Sorry, I definitely misrepresented what you said in my post. I should have phrased my post better.

The point I was trying to make was that the & operator in protocol composition is not an example of “inventing a slightly different syntax for a type-level operator”, but the opposite: it’s an example of an existing syntax being reused for a different type-level operation.

1 Like

Anything lowercase is technically a valid identifier. The problem with x is that it's sort of a 'variable name of art' in many domains, whereas of isn't, in fact I'm not sure I've ever seen it as an identifier.

That said...

This I do agree with.

Would there be any plans to add an ExpressibleByInlineArrayLiteral protocol in the future, like we have for strings, floats, arrays etc?

2 Likes

That's the issue. Why use notation that can be used as identifier when there are good alternatives for something as basic as fixed size array?

2 Likes

Since x cannot follow another identifier today, [x x Int] is unambiguous,1 but would clearly be hard to read. This is likely a hypothetical concern rather than a practical one. While x is used often in scratch code for a local variable, a more meaningful name is usually preferable, and this would be especially the case if it is found being used for the size of an array literal. In addition, while i , j , or n are often legitimate counters that might be suited to the size of an array, x is generally not used for such things.

I think choosing a syntax that’s so hostile to scratch code would be a mistake. While it’s true that more meaningful names are generally preferred, there are plenty of cases where scratch code makes more sense. For example, when you’re demonstrating an edge case in the language or when you’re reducing a bug down to the smallest amount of code possible.

You can find plenty of scratch code on this very forum.

4 Likes

9 posts were merged into an existing topic: Why did the InlineArray type sugar proposal come to review?

Oh, no. I did not mean that.

But, can you show some examples how you might use [x x Int] in real code?

I can imagine x being used as an index or the x coordinate of something, but not as the size of an array.

// unusual
let v: [x x Int]
// acceptable
let u: [m x Int]
let v: [n x Int]

I like both syntax sugar and also I feel like the things I work on would actually legitimately benefit from adopting this type. Nonetheless I don't actually see a good reason put forth in the proposal for why we actually want this. In fact I think any proposal with a motivation section structured like this one's should be returned for revision, because it has no actual exposition. Posting a toy example and going "this sucks" fundamentally misunderstands what the motivation section is. We're not building a language for toy examples! What is the real problem being solved here?

I trust that the authors of this proposal have actually consulted with people who have been using InlineArray and this is a response to the feedback they received. What were those problems? I can kind of try to guess them but I strongly suspect nobody told the authors that they need a better way to fill an InlineArray of 5 elements with 99. Do we want better zero initialization? Pattern initialization? When are people using nested InlineArrays? Would that be better served by a builder API that explicitly constructs an n-dimensional array and then lets type inference take over?

I do want to bring up that we don't have syntax sugar for creating Sets even though I am sure they get at least an order of magnitude more use than InlineArray, if not more. Why not? Is it because people don't make nested Sets very often? Is it because ArrayLiteralConvertible is usually good enough? I think there are a number of reasons why it doesn't but I want the same analysis for InlineArray done with real examples before we jump to "we absolutely have to have a prettier name for InlineArray" (which, I will note, is not "literal syntax", but the syntax of the type).

19 Likes

Well, it does see use publicly - and this is just a lower bound. (Apologies, you might have to fix the search from the link)

1 Like

The way I see it is this: heaps of people have clear issues with using x. Some it is stylistic, some it is syntax related, some it is for how it reads. It has been well established that it is not going to cause a parsing issue because an identifier followed by an identifier is not currently valid syntax.

If x is equally valid as of or by, but people just don't like x for even something as simple as stylistic reasons, that vote is valid enough to reject it.

Why is x superior to the alternatives?

So to flip it on its head, if the original pitch was to use by, and people were proposing x and because by just didn't look right, or was too often used as an identifier, but the original author just wanted by, and rejected using x because there was not valid syntactical reason to reject by, then we are just defaulting to the original pitch instead of deferring to the clear rejection of by (in the real world x).

I was equally shocked at this. It felt like a huge slap in the face to the clear majority that had issue with this. Again, I refer back to the likes that some of these posts are getting.

It's also interesting to me that the verbal reading of [5 x Int] is supposed(?) to be the word 'by', so why not just use the word by for its spelling?

Interestingly, the more I think of it, [5 by Int] really means [5 by 1 Units of Int]. So it makes sense that by or of could fill this role.

5 Likes

Note that this proposal is about the type syntax, not the value.

That said: if I grep for repeating: in any one of several medium size Swift projects, I get at least a hundred hits on every one. This to me weighs in favor of a syntax for it, as rust has (though rust's syntax is exclusively for its fixed array type, whereas the win would be to make a repeated value syntax work for multiple types).

This isn't an issue for sets, since they already have a value syntax (and it would be very unfortunate if they didn't)

Again not super scientific, but based on looking at the code I have to hand, about half the uses of repeating: are of "zero" values (if you include empty string and array, and .zero for more complex numeric types).

For one, Set is very short type name, and has only one generic argument. And yes, sets don't tend to be nested (dictionaries do tend to be nested, or rather dictionaries tend to have values of array types often, and so if we didn't have sugar for both, this would be much less nice than it is today).

Personally, I wouldn't be against sugar for set types. It might be nice to allow let set: {String}. The main thing that weighs against it is that

  • the current set value literal would be inconsistent (let set: {Int8} = [1,2,3] would be weird)
  • a future where we create a value version probably can't work (let set = {1,2,3} would be nice to avoid having to give type context, but the parsing ambiguities are likely insurmountable)
2 Likes

-1 on the proposed syntax:

  • x usage as a separator doesn't feel Swifty (it does feels childish).
  • do we need the separator here at all? [5 Int] looks alright.
  • if x meant to have a connotation of "inline-ness" (as it seems) it would be strange to use that as a shorthand syntax for initialising normal non-inlined arrays instead of .init(repeating:count:).

The last bullet point is in response to the future direction's:

3 Likes

I really dislike this, and have yet to hear a convincing argument why it is a goal to invoke some kind of association to multiplication. The proposed feature has little to do with multiplication anyways.

I think we should either use no delimiter, as in [5 Int] or 5[Int] — or use Rust's syntax [5; Int] of no other reason than to piggyback on existing terms of art.

The proposed syntax looks awful, imho.

4 Likes

There is no need to associate it with multiplication. (But being able to associate it with multiplication is a nice bonus). "5 times Int" has more clear meaning "5 of Int", "5 by Int" or "5 Int" and makes the intention more clear. Since typing times is long, and can cause even more name clashes than the other options considered, it makes sense to replace it with mathematical symbol of "times", ×. But since it is not easy to type it, one can type x instead as they look similar and x is often used when × symbol is not available. So there are sound reasons for using x besides being "cute".

But I do agree with you that [5; Int] is probably the best option, as it uses established notation from other PLs and avoids reinventing the wheel.

In my opinion, this syntax stems from a kind of "C envy" or maybe "Rust envy". We want to point at some code snippets and say we can do systems code, too.

While we do want Swift to be suitable for systems programming, our mission is different in that we want to do everything. That means this systems effort must consider the context of our existing language and as well as patching its flaws, we should reflect on and preserve its strengths.

Currently, in Swift, [SomeType] means a heap-allocated, dynamically-resizable Array -- and until recently, that was the only Array type Swift even exposed in the standard library at all. It's a good default, because Swift is a language based on the idea that everything is copyable (by default) and many language features will make copies implicitly just as you're trying to get stuff done, so indirect storage makes those copies cheap. While you do need to be a bit careful when writing mutating algorithms, on the whole it has been an enormously successful model and we're not at the end of the road with it yet; we know there are big improvements to the ARC optimiser coming that will eliminate loads more copies, and improvements to LLVM that will either eliminate or reduce the cost of a lot of bounds checking. If you need to get stuff done, it's great - it supports all the operations an array can support, language features like for loops and captures just work and are fast. You pretty much don't need to think about copying. Super convenient.

In Rust, let vals: [i32; 3] denotes an inline array, but Rust's array literal syntax is completely different from ours in just about every way you can think of:

// This means an inline array.
let vals_0: [i32; 3] = [1, 2, 3];

// 1. So, does [i32] mean a resizable, heap-allocated array?
let vals_1: [i32] = [1, 2, 3];

// Nope! Error - [i32] means a slice! And you need to spell it &[i32]

// 2. Okay so the type name is Vec<i32>. There is no shorthand.
// If we write that, can we initialise it with an array literal?
let vals_2: Vec<i32> = [1, 2, 3];

// Nope! Error - expected `Vec<i32>`, found `[{integer}; 3]`

// 3. In order to create a Vec from a literal, we *also* need the vec! macro.
let vals_3: Vec<i32> = vec![1, 2, 3];

// Now it finally works.

That kind of thing may be fine (or at least tolerable) for Rust developers, but it would be supremely inconvenient to any Swift developer. Swift is supposed to be a language that is approachable to people who have never done any programming before, or who are coming from languages such as Python or Javascript that are so abstract that they might not be familiar with the difference between inline vs indirect storage. They might not be the kind of people to post on these forums, but they're out there - I've even met some of them.

I think the literal syntax should spell out that this is a different kind of array. Maybe it will cause some sniggers in certain systems programmer circles that we need to spell out these kinds of details for the newbies, but they are one of our core constituencies.

13 Likes

I completely agree. Initially, I was merely against x for looking like a fake operator, but your argument is very strong: Given how much care has gone into naming the type and what were the reasons for it, I think the type needs to see the light of day for a while before we can make an adequate decision on what would be the best syntactic sugar for it.

4 Likes

Your suggested syntax is by far my most favorite so far because it solves a bunch of problems at once:

  • Lack of brackets beautifully indicates that there's no indirection and hints at potentially huge copying/refcounting overhead.
  • Without the brackets, there's less visual noise in the code.
  • The of contextual keyword is extremely descriptive for this purpose.
  • It's more compact than the proposed syntax: 5 of Int (8 characters) as opposed to [5 x Int] (9 characters).
4 Likes

-1 on the feature.

I'm confused why we're already discussing sugar syntax for a feature that hasn't been released yet. The only motivation in the pitch about this is the claim that "InlineArray<5, Int> is cumbersome".

As someone who eschews array sugar syntax and prefers to write Array<Int> everywhere, I find that not just unpersuasive, but anti-persuasive. There are genuine reasons for preferring the "more spelled out" way of writing a type, and dismissing those reasons with a handy-waving "cumbersome" is intellectually lazy, reductive, and slightly offensive. It is critical of an entire style of coding with no evidence to support the position.

Therefore, Hitchen's Razor applies:

What can be asserted without evidence can also be dismissed without evidence.

If you want to claim that this syntax is so superior, there needs to be real-world examples of people trying to use the InlineArray<5, Int> syntax and having that syntax be a problem. The proposal contains no such evidence beyond an appalling minimal two single-line examples that, for my style of writing code, look completely fine to me.

As such, I'm left with the conclusion that this proposal is unnecessary and adds complexity to the language for no benefit, because the proposal does not demonstrate the need for a fix.

In other words, this is a solution in search of an absent problem.

40 Likes

You're not alone! I am a huge fan of using typealiases for the purpose of increasing self-documenting nature of my code. It's amazing just how powerful a typealias can be for this purpose. Moreover, I strongly prefer avoiding plurals in names in favor of more clearly indicating the nature of the multitude (List for ordered collections, Set for unordered collections and static compositions).

consider

var memberList: [Int]

versus

var memberList: UserIDListIndexList

The former is utterly incomprehensible, while the latter leaves nothing to the imagination even with zero documentation.

With such an approach, collection syntax sugar is completely useless. I only use syntax sugar for collection types in prototype code, where I need to get things done fast and I don't care about maintainability.

1 Like