It's quite common to have non-commutative multiplication operations in mathematics (square matrix multiplication and quaternion multiplication, for example, are non-commutative). Formally, the bigger issue with these spellings is that n-ary repetition "ought to be" exponentiation in the type algebra, and concatenation "ought to be" multiplication instead of addition (which really should be commutative). Unfortunately for that ideologically pure line of reasoning, +
has been bound to array and string concatenation in Swift since ~forever.
.self
is the identity member, which exists on values of every type. Its purpose is not to serve as a semantic disambiguator for types, that's emergent behavior (and it's not really necessary or sufficient for that purpose). There is a syntactic rule necessary to disambiguate the use of <
as an operator from its use as an angle bracket for generic parameters; the parser looks for a matching >
followed by a member access (either (
for a constructor or .identifier
for a member lookup). .self
satisfies that requirement, and in order to avoid an even weirder rule that Int
is OK as a type reference but Foo<Int>
must be followed by .self
, we decided to require all type references to be part of a member access, with .self
as the way to get the metatype value. In retrospect, it would've been nicer to either have a proper type reference syntax, or fully commit to the "types are values" bit and ship SE-90.
The semantic ambiguity isn't necessarily a dealbreaker, since it already exists, but every new ambiguity we introduce makes the already-overworked type checker's job harder, so it's nice to avoid digging that ditch deeper.
I suspect that this is still generally sufficient:
let fiveIntegers: InlineArray<5, Int> = [1,2,3,4,5]
i.e. the value is already covered by existing array literal syntax. Is syntactic sugar over init(repeating:count:)
that much more valuable? And if so, is it generally valuable for values other than 0
?
If it's most valuable for zero-initialization, then perhaps we should give InlineArray
a convenience initializer when Element: Numeric
(or wherever .zero
is defined).
Or perhaps we should be looking at creating a marker protocol for DefaultInitializable
for any type that has an init()
member and give InlineArray
an init()
that delegates to Element.init()
.
Sorry for the misunderstanding. I meant a "sugar syntax" as a replacement of the InlineArray<5, Int>
part, not syntactic sugar over init(repeating:count:)
.
Indeed, this can be shortened even further in the case of it being initialized by a literal because the entire generic clause can be inferred:
let fiveIntegers: InlineArray = [1, 2, 3, 4, 5]
print(type(of: fiveIntegers)) // InlineArray<5, Int>
That doesn't help the case where it appears in a function/property signature though. If we consider what a fully generic function taking an InlineArray
might look like:
func sum<count: let Int, Element: BinaryInteger>(_ values: [count x Element]) { ... }
func sum<count: let Int, Element: BinaryInteger>(_ values: InlineArray<count, Element>) { ... }
I guess the only concern I would have about the sugar is that it doesn't look too much like a dictionary type at a quick glance. A well-named integer parameter (lowerCamelCase
) helps a lot there. That certainly puts the suggestion of using Rust's [n; T]
out of the running for me—it's far too close to [n: T]
, and I think that similarity would be a hinderance when quickly scanning code.
If using of
, it's not clear to me that any square brackets are required: 2 of Int
should suffice, as would then 2 of 3 of 4 of Int
, or x of y of z of Int
.
Valid. I was wondering this as well. I think it's because they'd want it to match aesthetically to the literal that would then constructs it.
Would there possibly be any other future use of a the proposed operator of
which would require something like []
to disambiguate it?
As a novice, how do I know to read 2 of 3 of 4 of Int
as InlineArray<2, InlineArray<3, InlineArray<4, Int>>>
instead of InlineArray<2 * 3 * 4, Int>
(or something else entirely?)
As with all syntax, you'd have to look it up if you're uncertain—is there any reason this would be of particular concern with or without a pair of square brackets?
I think it’s an argument for square brackets, personally. 2 * 3 * Int
and [2 * 3 * Int]
are equally ambiguous; [2 * [3 * Int]]
is not. Inside of the square brackets, the grammar could require expressions be parenthesized: [(2 * 3) * Int]
.
Considering the way we use ->
in function signatures and Foo & Bar
for protocol compositions, [3 x Int]
seems 'swifty' to me.
You would have to know that it has right associativity
(just like any operator). But since Xwu is suggesting that the []
would't be necessary, instead you could use ()
to explicitly direct associativity.
2 of 3 of 4 of Int
becomes (2 of (3 of (4 of Int)))
if you wanted to be more explicit.
I THINK (because I have seen some talk about [2 of 3] meaning InlineArray<2, Int> = [3,3]
) that you could otherwise can tell since Int
is in the expression and a type would be required to construct a InlineArray , that it couldn't be (((2 of 3) of 4) of Int)
since (2 of 3)
may not mean anything(?)
I think it is a good reason that we should NOT use *
and instead use of
. With of
one would not even need the []
.
AND Since of
is less likely to be used as a variable than x
. IMOP, of
should be elevated to preferred.
However,
I am a bit of an advocate for something more like the following.
As the above requires no new keywords. Thought I am skeptical if it collides with current grammar.
We have a precedent of not having specific literal syntax for sets:
let x: Set = [1, 2, 3]
where ": Set" bit is what makes it a Set. And Set
is arguably more frequently needed than InlineArray
, so not having a special inline array literal seems fine.
If to have syntax for inline array literals – my vote would be for them having square brackets.
The post above might got unnoticed as I've edited it a few times: tldr - it promotes [5]Int
syntax which solves the issue "what delimiter to use" (none) while using square brackets to highlight "array-ness", and having the count to the left which is very convenient for multidimensional case. As a bonus it expresses multidimensional arrays very easy: [5][6][7]Int
(although it's probably not good for the parser, e.g. [5][6] would be treated as "take 6th element of [5] array")
I don't see how anyone could read 2 of 3 of 4 of Int
as InlineArray<2 * 3 * 4, Int>
which is just InlineArray<24, Int>
, unless they somehow go the idea that of
also happened to be used when multiplying integers.
So the only reasonably guesses would be
InlineArray<2, InlineArray<3, InlineArray<4, Int>>>
and
InlineArray<InlineArray<InlineArray<2,<3,<4, Int>>>
These are of course isomorphic, so they would basically only differ in how you index them.
Remember also that dispensing with brackets is something we don't only do with commutative operators -- 2^3^4
is well defined -- so there is precedent for requiring people to remember which associativity an operator has, which is all that's needed here.
I would emphasize:
You and I have written a fair bit of Swift in our time, I'd guess. And we're participating in this thread, so we're likely going to both be aware of the final design of this feature and will internalize it quickly.
Somebody who's new to the language needs to understand that a) of
is an operator without much/relevant precedent in other languages, b) of
is right-associative, and c) of
behaves in some ways like *
(otherwise why are we discussing x
as a possible spelling here?) but in other ways not like it.
(And if the answer to this strawman is that 5 of Int
is an advanced feature, then why are we bothering with shorthand/sugar for it anyway?)
To be honest, I don't think even a novice would guess that 3 of 2
would mean 3 * 2
, especially since that word would only ever appear with a type at the end, like 3 of 4 of Int
. If they understand that the last of
creates a type, they will not think that the first of
is normal multiplication.
Not that I like the of
idea, are there any other infix keyword operators in swift?
as
and is
, and in
sort of.
Thanks for the feedback so far.
I wanted to leave a note regarding the future directions piece. Sometimes future directions are merely covering some future potential direction just as a matter of interest, or perhaps to head off discussing it in the pitch or proposal thread, keeping the discussion focused on what is being immediately proposed.
This future direction is a little different, in that I do think that if this type sugar were adopted, a similar value version should be introduced. As discussed in the pitch, this wouldn't just apply to InlineArray
, but to any array-like thing (Swift.Array
, Foundation.Data
) that can be initialized with a repeated value and a count.
An informal survey of some Swift projects I have on disk suggests that .init(repeating:count:)
is very common (a grep of the project I'm currently working on, that isn't that large, has > 100 instances of it). It is especially common in tests (which benefit greatly from being simple to write). I also believe (and realize this is very subjective, and many feel the exact opposite) that this is a case where brevity is clearer than verbosity.
var results: [[Presentation]] = [shapes.count x []]
reads much clearer to me than
var results: [[Presentation]] = .init(repeating: [], count: shapes.count)
This is not to say that it's a given that this value version idea would definitely be adopted in future, even if this proposal were accepted. But it is important that the future direction be included when making the choice of separator for the type form, to make sure that future direction is not closed off. Unless of course, the option that closes it off is just so good that it's worth closing off the future potential of the value form.
For this reason, I believe the [5 * Int]
form should be ruled out, because [5 * 10]
cannot be disambiguated in value form between "5 elements of value 10" and "1 element of value 50". And to my eye, [5 * Int]
is not so radically better than [5 x Int]
that it is worth ruling out forever the value form.[1]
personally I believe it's less good on a stand alone basis, but not so strongly that I can't see the appeal to some of
*
↩︎
This becomes more pronounced when dealing with multiple dimensions:
let fiveByFive: InlineArray<5, InlineArray<5, Int>> = .init(repeating: .init(repeating: 99))
maybe just use typealias
?
typealias Matrix5x5 = InlineArray<5, InlineArray<5, Int>>