Yet another fixed-size array spitball session


(Daryle Walker) #1

Static-Sized Arrays
This is a new proposed syntax for handling array types whose size is fixed at compile time. This is a classic kind of type that's still missing in Swift.

Like the built-in processor numeric types are mapped to special struct types, any built-in vector types should be mapped to special array types. There should be some sort of construct to allow parallel visitation of elements.

For a given array type A with an element type B, we have strideof(A) == COUNT * strideof(B), where COUNT is the number of elements in the array, which is the product of the lengths along each dimension. (A zero-dimension array has an element count of one.)

New Keywords
array
of
These keywords should be conditional if possible. The of shouldn't appear without following an array, so of should be easy to make conditional.

Immediate Arrays
Immediate arrays are a new kind of compound type. They have value semantics.

let x: array 3 of Int = [1, 2, 3]
var y: array 2 of array 5 of Double
Hopefully, this is simpler than my last proposal. I'm thinking that "array" is like a verb here.

The element type is at the end, so no parentheses (or similar) are needed. The syntax is easy to expand for nested array types. Here, the extents go from the widest span to the narrowest, like nested arrays in C. The inner element type is at the end instead of the start.

assert(x.0 == 1)
x.3 = 3 // ERROR!
y.1.3 = 3
y.0 = [-3, -2, -1, 0, +1]
Elements are addressed just like in tuples.

Nominal Arrays
Nominal arrays are a new kind of named type. They have value semantics.

array MyArray: (0..<6) of Int {
    /* ... */
}
I'm thinking "array" is like an adjective (or noun) here. The base-and-protocol list must have a shape specifier as its first item.

shape-specifier → ( extents-list_opt ) of type

extents-list → extent

extents-list → extent , extents-list

extent → type storage-rank_opt

extent → expression ..< expression storage-rank_opt

extent → expression ... expression storage-rank_opt

storage-rank → : expression
A type used for an extent:

Must be a raw-value enumeration type.
The raw-value type must be one of the default integer types.
There must be at least one case, and all the cases must form a contiguous range of values.
A range used for an extent:

Must use for its boundary type either:
a default integer type, or
an enumeration type that could qualify as an extent type.
Must be a valid range with at least one element.
The expression for a storage rank must evaluate to a compile-time integer value that is at least zero and less than the number of extents. An extent without an explicit storage rank gets one equal to the smallest valid value yet unused. (Multiple extents without explicit ranks are finalized in lexical order.)

The extent with the largest storage rank has elements with adjacent indexes (assuming all other extents use fixed indexes) placed adjacent in memory. The extent with storage rank 0 have elements with adjacent indexes (assuming all other extents use fixed indexes) the furthest span apart in memory.

The total number of elements for the array is the product of the lengths of the extents. A zero-dimension array has a singular element.

A nominal array type can have type-level members and computed instance-level properties, just like the other named types. The initial set of members, if not overridden, are:

A type-alias Element that refers to the element type.
A type-alias Index that refers to a tuple composed of the extent types. For a range-based extent, the corresponding type is its boundary type.
If the element type is default-initializable, a default initializer.
An initializer that takes as its only parameter an Element, which is copied to each element.
An initializer that takes as its only parameter a closure that takes as its only parameter an Index and returns the value the corresponding element starts with.
An initializer similar to the previous, but the closure takes the index tuple's parameters separately.
Two subscript members, with get and set modes, to handle dereferencing. One member takes an Index, the other an exploded index list. It's an error to refer to an invalid index location.
Flexible Array Reference
To not have to fix a function for each extent shape, something similar to the old T[] array segment idea from C(++) is needed:

func foo(x: array of Int) -> Int
func bar(y: array of mutating Double) -> Int
Without a size given inside the array declaration, these functions can work with any array with the given element type. Array-segment references are reference types.

An array-segment reference conforms to RandomAccessCollection. A reference that is marked mutating also conforms to MutableCollection. (Should we create a new mutable keyword instead?)

A function that returns or otherwise writes out an array-segment reference must ensure it points to memory that will survive the function's end. (I'm not sure we should enforce some sort of retain/ARC instead.) (Should we ban these instead?)

An array-segment reference can be generated from an immediate array or a nominal array using as. A reference can be mutatingif the source array starts in var mode. Array-segment references access their elements in storage order.

Conversions
These conversions can work through as:

array N of T → array of T
array N of T → array of mutating T, if the source array was in var mode
array N of T → array M * N of U, when T is an array M of U
MyArray → array of MyArray.Element
MyArray → array of mutating MyArray.Element, if the source array was in var mode.
array of mutating T → array of T
array of T → array of U, where U is the element type of T
array of mutating T → array of mutating U, where U is the element type of T
Any conversion that requires a chain of these is also legal. When a function has an array-segment reference as a parameter, and the argument is an array with the same element type, the argument doesn't need an explicit as.

(Should we allow the reverse-direction transformations, or even chains of them, with "as!" and/or "as?"? Note that this could end up converting an array 5 of Int to an array 3 of array 2 of Int with its last Int nested element invalid.)

Parallel/Vector Processing (only semi-serious)
As the processor's built-in numeric types are mapped to the default numeric types, we should do something similar for a processor's built-in vector numeric types. Like a processor that has four integers as a vector, we could define a Int_4 structure type that maps to it.

We also need a way to possibly do evaluations in parallel. What about:

do x, y, z as array {
    z = x == y
    x += y
}
The three objects between do and as have to have the same shape. Within the block, the object names now refer to corresponding individual elements.

Extra Traits
As I was writing this, I came up with new functionality and corresponding keywords. Should we have an #extentsof(type)to use an extent shape from one nominal array type to another? Should we have an #extentscount(type)?

Example: take the do as array block. If x and y were function parameters, but z was an internal object, how would we define z besides manually copying the shape list?

···


Daryle Walker
Mac, Internet, and Video Game Junkie
darylew AT mac DOT com


(Rien) #2

While I’d like a fixed size array, I’d agree with Rod that this looks odd.

Myself I use the name ‘array’ a lot in places where I get/need a temporary array that lives for a few lines only. So I am against using the keyword ‘array’.

A name like SizedArray would seem more in-line with other Swift types.

Also, there is a standard way of looking up indicies, and it uses the ‘[]’ signs. Why did you drop that?
I would expect confusion when looking up a variable index like: let c = x.myIndex
let c = x[myIndex] looks decidedly more clear to me.

Btw, what to think of:

var z: array 2 of Array<array 5 of Double>

(that looks pretty horrible to me)

Regards,
Rien

Site: http://balancingrock.nl
Blog: http://swiftrien.blogspot.com
Github: http://github.com/Balancingrock
Project: http://swiftfire.nl - A server for websites build in Swift

···

On 29 May 2017, at 08:37, Daryle Walker via swift-evolution <swift-evolution@swift.org> wrote:

Static-Sized Arrays
This is a new proposed syntax for handling array types whose size is fixed at compile time. This is a classic kind of type that's still missing in Swift.

Like the built-in processor numeric types are mapped to special struct types, any built-in vector types should be mapped to special array types. There should be some sort of construct to allow parallel visitation of elements.

For a given array type A with an element type B, we have strideof(A) == COUNT * strideof(B), where COUNT is the number of elements in the array, which is the product of the lengths along each dimension. (A zero-dimension array has an element count of one.)

New Keywords
  • array
  • of
These keywords should be conditional if possible. The of shouldn't appear without following an array, so of should be easy to make conditional.

Immediate Arrays
Immediate arrays are a new kind of compound type. They have value semantics.

let x: array 3 of Int = [1, 2, 3]
var y: array 2 of array 5 of Double

Hopefully, this is simpler than my last proposal. I'm thinking that "array" is like a verb here.

The element type is at the end, so no parentheses (or similar) are needed. The syntax is easy to expand for nested array types. Here, the extents go from the widest span to the narrowest, like nested arrays in C. The inner element type is at the end instead of the start.

assert(x.0 == 1)
x.3 = 3 // ERROR!
y.1.3 = 3
y.0 = [-3, -2, -1, 0, +1]

Elements are addressed just like in tuples.

Nominal Arrays
Nominal arrays are a new kind of named type. They have value semantics.

array MyArray: (0..<6) of Int {
    /* ... */
}

I'm thinking "array" is like an adjective (or noun) here. The base-and-protocol list must have a shape specifier as its first item.

shape-specifier → ( extents-list_opt ) of type

extents-list → extent

extents-list → extent , extents-list

extent → type storage-rank_opt

extent → expression ..< expression storage-rank_opt

extent → expression ... expression storage-rank_opt

storage-rank → : expression
A type used for an extent:

  • Must be a raw-value enumeration type.
  • The raw-value type must be one of the default integer types.
  • There must be at least one case, and all the cases must form a contiguous range of values.
A range used for an extent:

  • Must use for its boundary type either:
    • a default integer type, or
    • an enumeration type that could qualify as an extent type.
  • Must be a valid range with at least one element.
The expression for a storage rank must evaluate to a compile-time integer value that is at least zero and less than the number of extents. An extent without an explicit storage rank gets one equal to the smallest valid value yet unused. (Multiple extents without explicit ranks are finalized in lexical order.)

The extent with the largest storage rank has elements with adjacent indexes (assuming all other extents use fixed indexes) placed adjacent in memory. The extent with storage rank 0 have elements with adjacent indexes (assuming all other extents use fixed indexes) the furthest span apart in memory.

The total number of elements for the array is the product of the lengths of the extents. A zero-dimension array has a singular element.

A nominal array type can have type-level members and computed instance-level properties, just like the other named types. The initial set of members, if not overridden, are:

  • A type-alias Element that refers to the element type.
  • A type-alias Index that refers to a tuple composed of the extent types. For a range-based extent, the corresponding type is its boundary type.
  • If the element type is default-initializable, a default initializer.
  • An initializer that takes as its only parameter an Element, which is copied to each element.
  • An initializer that takes as its only parameter a closure that takes as its only parameter an Index and returns the value the corresponding element starts with.
  • An initializer similar to the previous, but the closure takes the index tuple's parameters separately.
  • Two subscript members, with get and set modes, to handle dereferencing. One member takes an Index, the other an exploded index list. It's an error to refer to an invalid index location.
Flexible Array Reference
To not have to fix a function for each extent shape, something similar to the old T[] array segment idea from C(++) is needed:

func foo(x: array of Int) -> Int
func bar(y: array of mutating Double) -> Int

Without a size given inside the array declaration, these functions can work with any array with the given element type. Array-segment references are reference types.

An array-segment reference conforms to RandomAccessCollection. A reference that is marked mutating also conforms to MutableCollection. (Should we create a new mutable keyword instead?)

A function that returns or otherwise writes out an array-segment reference must ensure it points to memory that will survive the function's end. (I'm not sure we should enforce some sort of retain/ARC instead.) (Should we ban these instead?)

An array-segment reference can be generated from an immediate array or a nominal array using as. A reference can be mutatingif the source array starts in var mode. Array-segment references access their elements in storage order.

Conversions
These conversions can work through as:

  • array N of T → array of T
  • array N of T → array of mutating T, if the source array was in var mode
  • array N of T → array M * N of U, when T is an array M of U
  • MyArray → array of MyArray.Element
  • MyArray → array of mutating MyArray.Element, if the source array was in var mode.
  • array of mutating T → array of T
  • array of T → array of U, where U is the element type of T
  • array of mutating T → array of mutating U, where U is the element type of T
Any conversion that requires a chain of these is also legal. When a function has an array-segment reference as a parameter, and the argument is an array with the same element type, the argument doesn't need an explicit as.

(Should we allow the reverse-direction transformations, or even chains of them, with "as!" and/or "as?"? Note that this could end up converting an array 5 of Int to an array 3 of array 2 of Int with its last Int nested element invalid.)

Parallel/Vector Processing (only semi-serious)
As the processor's built-in numeric types are mapped to the default numeric types, we should do something similar for a processor's built-in vector numeric types. Like a processor that has four integers as a vector, we could define a Int_4 structure type that maps to it.

We also need a way to possibly do evaluations in parallel. What about:

do x, y, z as array {
    z = x == y
    x += y
}

The three objects between do and as have to have the same shape. Within the block, the object names now refer to corresponding individual elements.

Extra Traits
As I was writing this, I came up with new functionality and corresponding keywords. Should we have an #extentsof(type)to use an extent shape from one nominal array type to another? Should we have an #extentscount(type)?

Example: take the do as array block. If x and y were function parameters, but z was an internal object, how would we define z besides manually copying the shape list?


Daryle Walker
Mac, Internet, and Video Game Junkie
darylew AT mac DOT com

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Rod Brown) #3

While I can’t comment about the technical details, I have to say, this looks really “unswifty” to my eye. I’d probably expect something more along the lines of:

  let x: FixedArray<Int> = [1, 2, 3]

or:

  let y: StaticArray<Int> = [1, 2, 3]

···

On 29 May 2017, at 4:37 pm, Daryle Walker via swift-evolution <swift-evolution@swift.org> wrote:

Static-Sized Arrays
This is a new proposed syntax for handling array types whose size is fixed at compile time. This is a classic kind of type that's still missing in Swift.

Like the built-in processor numeric types are mapped to special struct types, any built-in vector types should be mapped to special array types. There should be some sort of construct to allow parallel visitation of elements.

For a given array type A with an element type B, we have strideof(A) == COUNT * strideof(B), where COUNT is the number of elements in the array, which is the product of the lengths along each dimension. (A zero-dimension array has an element count of one.)

New Keywords
array
of
These keywords should be conditional if possible. The of shouldn't appear without following an array, so of should be easy to make conditional.

Immediate Arrays
Immediate arrays are a new kind of compound type. They have value semantics.

let x: array 3 of Int = [1, 2, 3]
var y: array 2 of array 5 of Double
Hopefully, this is simpler than my last proposal. I'm thinking that "array" is like a verb here.

The element type is at the end, so no parentheses (or similar) are needed. The syntax is easy to expand for nested array types. Here, the extents go from the widest span to the narrowest, like nested arrays in C. The inner element type is at the end instead of the start.

assert(x.0 == 1)
x.3 = 3 // ERROR!
y.1.3 = 3
y.0 = [-3, -2, -1, 0, +1]
Elements are addressed just like in tuples.

Nominal Arrays
Nominal arrays are a new kind of named type. They have value semantics.

array MyArray: (0..<6) of Int {
    /* ... */
}
I'm thinking "array" is like an adjective (or noun) here. The base-and-protocol list must have a shape specifier as its first item.

shape-specifier → ( extents-list_opt ) of type

extents-list → extent

extents-list → extent , extents-list

extent → type storage-rank_opt

extent → expression ..< expression storage-rank_opt

extent → expression ... expression storage-rank_opt

storage-rank → : expression
A type used for an extent:

Must be a raw-value enumeration type.
The raw-value type must be one of the default integer types.
There must be at least one case, and all the cases must form a contiguous range of values.
A range used for an extent:

Must use for its boundary type either:
a default integer type, or
an enumeration type that could qualify as an extent type.
Must be a valid range with at least one element.
The expression for a storage rank must evaluate to a compile-time integer value that is at least zero and less than the number of extents. An extent without an explicit storage rank gets one equal to the smallest valid value yet unused. (Multiple extents without explicit ranks are finalized in lexical order.)

The extent with the largest storage rank has elements with adjacent indexes (assuming all other extents use fixed indexes) placed adjacent in memory. The extent with storage rank 0 have elements with adjacent indexes (assuming all other extents use fixed indexes) the furthest span apart in memory.

The total number of elements for the array is the product of the lengths of the extents. A zero-dimension array has a singular element.

A nominal array type can have type-level members and computed instance-level properties, just like the other named types. The initial set of members, if not overridden, are:

A type-alias Element that refers to the element type.
A type-alias Index that refers to a tuple composed of the extent types. For a range-based extent, the corresponding type is its boundary type.
If the element type is default-initializable, a default initializer.
An initializer that takes as its only parameter an Element, which is copied to each element.
An initializer that takes as its only parameter a closure that takes as its only parameter an Index and returns the value the corresponding element starts with.
An initializer similar to the previous, but the closure takes the index tuple's parameters separately.
Two subscript members, with get and set modes, to handle dereferencing. One member takes an Index, the other an exploded index list. It's an error to refer to an invalid index location.
Flexible Array Reference
To not have to fix a function for each extent shape, something similar to the old T[] array segment idea from C(++) is needed:

func foo(x: array of Int) -> Int
func bar(y: array of mutating Double) -> Int
Without a size given inside the array declaration, these functions can work with any array with the given element type. Array-segment references are reference types.

An array-segment reference conforms to RandomAccessCollection. A reference that is marked mutating also conforms to MutableCollection. (Should we create a new mutable keyword instead?)

A function that returns or otherwise writes out an array-segment reference must ensure it points to memory that will survive the function's end. (I'm not sure we should enforce some sort of retain/ARC instead.) (Should we ban these instead?)

An array-segment reference can be generated from an immediate array or a nominal array using as. A reference can be mutatingif the source array starts in var mode. Array-segment references access their elements in storage order.

Conversions
These conversions can work through as:

array N of T → array of T
array N of T → array of mutating T, if the source array was in var mode
array N of T → array M * N of U, when T is an array M of U
MyArray → array of MyArray.Element
MyArray → array of mutating MyArray.Element, if the source array was in var mode.
array of mutating T → array of T
array of T → array of U, where U is the element type of T
array of mutating T → array of mutating U, where U is the element type of T
Any conversion that requires a chain of these is also legal. When a function has an array-segment reference as a parameter, and the argument is an array with the same element type, the argument doesn't need an explicit as.

(Should we allow the reverse-direction transformations, or even chains of them, with "as!" and/or "as?"? Note that this could end up converting an array 5 of Int to an array 3 of array 2 of Int with its last Int nested element invalid.)

Parallel/Vector Processing (only semi-serious)
As the processor's built-in numeric types are mapped to the default numeric types, we should do something similar for a processor's built-in vector numeric types. Like a processor that has four integers as a vector, we could define a Int_4 structure type that maps to it.

We also need a way to possibly do evaluations in parallel. What about:

do x, y, z as array {
    z = x == y
    x += y
}
The three objects between do and as have to have the same shape. Within the block, the object names now refer to corresponding individual elements.

Extra Traits
As I was writing this, I came up with new functionality and corresponding keywords. Should we have an #extentsof(type)to use an extent shape from one nominal array type to another? Should we have an #extentscount(type)?

Example: take the do as array block. If x and y were function parameters, but z was an internal object, how would we define z besides manually copying the shape list?


Daryle Walker
Mac, Internet, and Video Game Junkie
darylew AT mac DOT com

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Pavol Vaskovic) #4

I'm sorry if I'm misunderstanding your proposal, but I think Swift already
has "array types whose size is fixed at compile time" called Tuple.

You seem to go straight into proposing new language syntax, without
explaining the motivation. If there were previous discussions of this
topic, which do that here on the list, could you please provide links?

Best regards
Pavol Vaskovic


(Haravikk) #5

Just wanted to add my two-cents to this discussion, but in terms of syntax I think that the basic method for specifying size should be with some kind of type variables, like so:

  struct MyFixedArray<T, size:Int> { … }

The idea being that we can then use size as a variable anywhere within the code for MyFixedArray, but unlike other variables it is determined at compile time, so should be optimised accordingly. As with generics, setting a type variable effectively creates a variant type so for example, a MyFixedArray<Int, size:3> and a MyFixedArray<Int, size:4> would no longer be directly compatible as they may differ internally.

This would be useful not just for fixed arrays, but other possibly type variations as well. Ideally it should be possible to specify a default value for type variables; while this wouldn't be useful for fixed arrays, it may be useful for other type variants.

To better suit the specific use-case of arrays, we could also add some useful attributes or keywords, for example, reusing subscript could give us:

struct MyFixedArray<T, subscript size:Int> { … }
let foo:MyFixedArray[4] = [1, 2, 3, 4] // with T being inferred, this is a shorthand for specifying a size of 4

Type variables could also be usable with generics, like-so:

// only accept fixed arrays of same or smaller size:
func myMethod<FA>(values:FA) where FA:FixedArray, FA.size <= Self.size { … }

These are the kind of general purpose capabilities I'd like to see at some point, as the usefulness extends beyond just fixed-size arrays.

However, on the subject of fixed-size arrays specifically, one other possibility to explore is the concept of Tuple repetition and subscripting. For example, it would be interesting if we could do things like:

  var foo:(Int)[4] = 0 // Initialises four Ints, all set initially to zero
  for i in 0 ..< 4 { foo[i] = i } // values are no 0, 1, 2, 3

This can be especially interesting when you get into more complex tuples like so:

  var bar:(x:Int, y:Int)[10] = (x: 0, y: 0) // Initialises 10 pairs of Ints, all initially set to zero
  for i in 0 ..< 10 { bar[i].x = i; bar[i].y = i } // here we need to use .x and .y to access the values of each pair

Of course this would have the same performance characteristics as standard tuples, but it's an interesting means for creating quick, fixed-size arrays in a flexible way. Part of the idea here is that by subscripting tuples, we avoid the need for a general subscript on all types, keeping that available for use-cases like the above.


(Brent Royal-Gordon) #6

My preference would still be to build this from four separate features:

1. Magic tuple conformances: We already want to be able to automatically conform tuples to protocols like Equatable, Hashable, and Comparable. These can all be compiler magic; they don't have to be definable in userspace.

2. Conform tuples to Collection: The Element type should be the most specific common supertype of the tuple's elements. If all the elements are the same type, it would be that type. The Index and IndexDistance types should be Int.

3. Conform same-type tuples to MutableCollection: If all elements are the same type, you can also modify the values. (If their types vary in any way, however, it would not be safe to allow mutations, since you could assign the wrong type to an element.)

3. Add sugar for a tuple of N identical elements: Probably something like `4 * Int`, but opinions can vary.

This solution avoids adding another structural type to the language or introducing non-type generic parameters. It also addresses other needs: 1 and 2 are desirable features in their own right which address other use cases in addition to this one. And it's nicely incremental, which is always a plus.

···

On May 28, 2017, at 11:37 PM, Daryle Walker via swift-evolution <swift-evolution@swift.org> wrote:

Static-Sized Arrays

--
Brent Royal-Gordon
Architechies


(Daryle Walker) #7

New Spitball

But first:

C has one of the worst array presentation models. It has traumatized so many that those among them that grew up to be language designers themselves threw out fixed-sized arrays entirely (from Java to even current Swift!). But we’re getting through to everyone that the concept is still important, no matter how bad one realization of it is.

* If you could to arrays over, why limit your indexes to 0 ..< COUNT, when you could use any range by subtracting an offset first
* Once you do that, why limit yourself to Int, when anything that could be implemented as an Int (like an enumeration type) would also work.

The whole point of adding types is abstraction. If you are modeling a table where the coordinates are an enumeration, why not let the language to the translation for you. There’s no need for just a Swift-y version of C’s array model. (Already Swift downplays pointers, so the “arrays are pointers with funny settings” part of the C model shouldn’t be carried over.)

This isn’t a new concept; while C’s successors gave up, C’s contemporaries didn’t. There’s plenty of models to base array presentation on. Looking at an Ada page recently, I realized that I must have let my previous experience shape the keywords I chose.

Now back to Spitball #3:

New Keywords:
* of
* #indexOf
* #flatten

Static Array Directive:
* “[“ Specifiers_opt “of” type “]”
* Specifiers: Specifier | Specifier “,” Specifiers
* Specifier: Extent-Range Storage-Rank_opt
* Extent-Range: Low … High | Low ..< High | N (for 0 ..< N) | Enum-Type (for Enum.min … Enum.max)
* Storage-Rank: Integer in 0 ..< ExtentCount; use smallest unused value if missing; all values must be used exactly once
* “mutating” (or “nonmutating”) can precede the type if the directive is used for an array-segment reference

Immediate Array: Use Static Array Directive with at least one extent

Array-Segment Reference: Use Static Array Directive with no extents
* can initialize a scoped reference with an array literal, but the reference must be in “let” mode (so you can’t reseat it and lose the only reference to the array)
* If you use this construct, put in a “mutating” to let the elements be changeable if needed
* This will let you use a C-like “let myArray: [of mutating Int] = [1, 2, 3, 4, 6]” without counting first, like “var myArray: [5 of Int] = [1, 2, 3, 4, 6]” would need

Nominal Arrays: Use “struct” with a Static Array Directive as the base type; mutually exclusive with instance-level stored properties (Having neither is OK.)
* The “subscript” definition allows zero parameters, but “[]” doesn’t allow zero arguments, so use “myArray.super” to get the inner singular value in this case
* Swift allows “self” by itself, but not “super”. This will have to be changed.

For loops:
* Array-segment references conform to RandomAccessCollection (and possibly MutableCollection); the two definitive array types don’t
* For loops will work for the definitive array types anyway
* We should either define iteration in storage order or let it be implementation-defined
* We should have some other syntax to let processing of elements go in parallel
* Within a for-loop, use “#indexOf(X)” to get a tuple of the index coordinates of the current element; “X” is (one of) the iteration objects between the “for” and “in”; an identifier is needed so if you have nested for-loops with separate arrays, you can choose which one to inspect

Dereferencing:
* For immediate arrays, use "myArray.0" like tuples, it’s "myArray.(1, 2)" for multi-dimensional arrays
* Convert to array-segment first if you need run-time index selection (with “[]”)
* Nominal arrays come with “[]” by default

Function arguments:
* Use array-segment reference parameter to take definitive arrays of any size, assuming the same element type
* If the definitive array is multi-dimensional, use “#flatten(myArray)” first (I’ve realized that a multi-D array should not translate to a linear ASR silently by default.)
* Of course, you can use either definitive array type as a parameter too

···


Daryle Walker
Mac, Internet, and Video Game Junkie
darylew AT mac DOT com


(Tino) #8

I agree strongly that the syntax looks awkward — imho
var v: Vector<Int, size: 3>
would be a much better fit than
var v array 3 of Int

As much as I want to have "real" arrays, I don't think we should add new keywords for them.


(Daryle Walker) #9

Generic syntax is intentionally not used. It has the same problem that the tuple-appearing suggestion I wrote last time had: expanding nested arrays going inward instead of rightward looks awful.

Are these examples supposed to be array-segment references? Otherwise you’re missing the extent length. (Remember: generics only take in types; we don’t do value-based generic parameters like C++ does (yet).) We could put in a rule that a array-segment reference that is initialized with an array expression can be freely converted/considered to be an immediate array of the same type and now-explicit length.

···

On May 29, 2017, at 3:21 AM, Rod Brown rodney.brown6@icloud.com wrote:

While I can’t comment about the technical details, I have to say, this looks really “unswifty” to my eye. I’d probably expect something more along the lines of:

let x: FixedArray = [1, 2, 3]

or:

let y: StaticArray = [1, 2, 3]

Daryle Walker
Mac, Internet, and Video Game Junkie
darylew AT mac DOT com


(Daryle Walker) #10

While I’d like a fixed size array, I’d agree with Rod that this looks odd.

Myself I use the name ‘array’ a lot in places where I get/need a temporary array that lives for a few lines only. So I am against using the keyword ‘array’.

That’s why I hope “array” can be made a conditional keyword, for backward compatibility. Any other keyword, for this or any other proposal, will also have this problem.

A name like SizedArray would seem more in-line with other Swift types.

I originally was going to have fixed arrays be a “struct” with funny settings. After thinking on how I would define the shape directive and deal with any potential problems, I reconsidered and made fixed arrays be a separate kind of type. For example, enumerations are distinct kinds of types and not SwFS-es either (no matter how they’re internally implemented).

Also, there is a standard way of looking up indicies, and it uses the ‘[]’ signs. Why did you drop that?
I would expect confusion when looking up a variable index like: let c = x.myIndex
let c = x[myIndex] looks decidedly more clear to me.

Array-segment references (via “Collection”) and nominal arrays (in my list of default members) do support the subscript operator. Immediate arrays don’t, because they are supposed to model homogenous tuples, and tuples are generally closed to extensions.

You can’t use a variable index with an immediate array, just like you can’t with a tuple. (You can if you make an array-segment reference from the immediate array first.)

Oh, I forgot to mention that you can use a “for”-loop with arrays. Iteration with immediate and nominal arrays work as if they were converted to an array-segment reference first. (But I think we should require an explicit conversion if you want to use “.enumerated()” or other “Collection” members, because the direct fixed array types don’t have those members.) I say “as if” so the implementors can be free to do unwrapping or any other optimizations having the elements on the stack can give.

Btw, what to think of:

var z: array 2 of Array<array 5 of Double>

(that looks pretty horrible to me)

If you use nested arrays where some extents have to flexible and others are fixed, and you want the efficiencies from not using the heap for the fixed dimensions, this can’t be avoided.

···

On May 29, 2017, at 3:37 AM, Rien Rien@Balancingrock.nl wrote:

Daryle Walker
Mac, Internet, and Video Game Junkie
darylew AT mac DOT com


(David Sweeris) #11

Tuples don't support subscripting or protocol conformance. Or an easy syntax to declare/initialize them... `(Int, Int, Int, Int)` isn't *that* horrible compared to "[Int x 4]", but would you want to replace "[Int8 x 10000]" with the multipage-long tuple equivalent?

- Dave Sweeris

···

On May 29, 2017, at 22:03, Pavol Vaskovic via swift-evolution <swift-evolution@swift.org> wrote:

I'm sorry if I'm misunderstanding your proposal, but I think Swift already has "array types whose size is fixed at compile time" called Tuple.


(Pavol Vaskovic) #12

:flushed:
It would be really helpful to my understanding, if you spoke about a
practical use case. This reads as a contrived counterexample to me…

If your type really has 10 000 values in it, why does it have to be static,
why doesn't normal Array fit the bill?

--Pavol

···

On Tue, May 30, 2017 at 7:51 AM, David Sweeris <davesweeris@mac.com> wrote:

`(Int, Int, Int, Int)` isn't *that* horrible compared to "[Int x 4]", but
would you want to replace "[Int8 x 10000]" with the multipage-long tuple
equivalent?


(Zaid Daghestani) #13

Just a small question, named tuples => Dictionary? Or how would that resolve?

···

On Jun 2, 2017, at 2:20 AM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

On May 28, 2017, at 11:37 PM, Daryle Walker via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Static-Sized Arrays

My preference would still be to build this from four separate features:

1. Magic tuple conformances: We already want to be able to automatically conform tuples to protocols like Equatable, Hashable, and Comparable. These can all be compiler magic; they don't have to be definable in userspace.

2. Conform tuples to Collection: The Element type should be the most specific common supertype of the tuple's elements. If all the elements are the same type, it would be that type. The Index and IndexDistance types should be Int.

3. Conform same-type tuples to MutableCollection: If all elements are the same type, you can also modify the values. (If their types vary in any way, however, it would not be safe to allow mutations, since you could assign the wrong type to an element.)

3. Add sugar for a tuple of N identical elements: Probably something like `4 * Int`, but opinions can vary.

This solution avoids adding another structural type to the language or introducing non-type generic parameters. It also addresses other needs: 1 and 2 are desirable features in their own right which address other use cases in addition to this one. And it's nicely incremental, which is always a plus.

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


#14

My favorite proposal so far is one that was posted a while ago, [Int * 4]. I think that this syntax looks pretty Swifty. There isn't an oddball subscript operator like with Int[4], and there isn't a need to allow generics to support values as parameters. It's clear that [Int * 4] will be array-like and contain four Ints. For multidimensional arrays we could use [Int * (2,3)]. For an array of tuples, [(x: Int, y: Int) * 4]. I'm not sure why nested static arrays would be needed when multidimentionality is provided out of the box, but presumably the syntax would support them; you would just need to subscript the arrays one at a time instead of with a single subscript operator, e.g., a[i][j] instead of a[i, j].

I'm imagining this syntax working like [T] does now as a shorthand for Array<T>, although I'm not sure what [Int * 4] should be short for (StaticArray<Int, 4>? Vector4<Int>? ...). Constructors for static arrays would be called using [Int * 4](args). I'm thinking the constructors would be [T * 4](filledWith: T), [T * 4](filledBy: (Int)->T), and an initializer taking a Sequence that fails in some manner on a dimension mismatch.

···

On Jun 1, 2017, at 7:36 AM, Haravikk via swift-evolution <swift-evolution@swift.org> wrote:

Just wanted to add my two-cents to this discussion, but in terms of syntax I think that the basic method for specifying size should be with some kind of type variables, like so:

  struct MyFixedArray<T, size:Int> { … }

The idea being that we can then use size as a variable anywhere within the code for MyFixedArray, but unlike other variables it is determined at compile time, so should be optimised accordingly. As with generics, setting a type variable effectively creates a variant type so for example, a MyFixedArray<Int, size:3> and a MyFixedArray<Int, size:4> would no longer be directly compatible as they may differ internally.

This would be useful not just for fixed arrays, but other possibly type variations as well. Ideally it should be possible to specify a default value for type variables; while this wouldn't be useful for fixed arrays, it may be useful for other type variants.

To better suit the specific use-case of arrays, we could also add some useful attributes or keywords, for example, reusing subscript could give us:

struct MyFixedArray<T, subscript size:Int> { … }
let foo:MyFixedArray[4] = [1, 2, 3, 4] // with T being inferred, this is a shorthand for specifying a size of 4

Type variables could also be usable with generics, like-so:

// only accept fixed arrays of same or smaller size:
func myMethod<FA>(values:FA) where FA:FixedArray, FA.size <= Self.size { … }

These are the kind of general purpose capabilities I'd like to see at some point, as the usefulness extends beyond just fixed-size arrays.

However, on the subject of fixed-size arrays specifically, one other possibility to explore is the concept of Tuple repetition and subscripting. For example, it would be interesting if we could do things like:

  var foo:(Int)[4] = 0 // Initialises four Ints, all set initially to zero
  for i in 0 ..< 4 { foo[i] = i } // values are no 0, 1, 2, 3

This can be especially interesting when you get into more complex tuples like so:

  var bar:(x:Int, y:Int)[10] = (x: 0, y: 0) // Initialises 10 pairs of Ints, all initially set to zero
  for i in 0 ..< 10 { bar[i].x = i; bar[i].y = i } // here we need to use .x and .y to access the values of each pair

Of course this would have the same performance characteristics as standard tuples, but it's an interesting means for creating quick, fixed-size arrays in a flexible way. Part of the idea here is that by subscripting tuples, we avoid the need for a general subscript on all types, keeping that available for use-cases like the above.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Dave Abrahams) #15

I think any complete solution depends on at least two more things:

1. adding the ability to get an UnsafePointer to an immutable instance
   of value type

2. support for leaving some of the elements uninitialized, or
   alternatively, a variation of this type that is RangeReplaceable but
   also bounded in capacity.

···

on Fri Jun 02 2017, Brent Royal-Gordon <swift-evolution@swift.org> wrote:

On May 28, 2017, at 11:37 PM, Daryle Walker via swift-evolution >> <swift-evolution@swift.org> wrote:

Static-Sized Arrays

My preference would still be to build this from four separate features:

1. Magic tuple conformances: We already want to be able to
automatically conform tuples to protocols like Equatable, Hashable,
and Comparable. These can all be compiler magic; they don't have to be
definable in userspace.

2. Conform tuples to Collection: The Element type should be the most
specific common supertype of the tuple's elements. If all the elements
are the same type, it would be that type. The Index and IndexDistance
types should be Int.

3. Conform same-type tuples to MutableCollection: If all elements are
the same type, you can also modify the values. (If their types vary in
any way, however, it would not be safe to allow mutations, since you
could assign the wrong type to an element.)

3. Add sugar for a tuple of N identical elements: Probably something
like `4 * Int`, but opinions can vary.

--
-Dave


(Tino) #16

1. Magic tuple conformances: We already want to be able to automatically conform tuples to protocols like Equatable, Hashable, and Comparable. These can all be compiler magic; they don't have to be definable in userspace.

2. Conform tuples to Collection: The Element type should be the most specific common supertype of the tuple's elements. If all the elements are the same type, it would be that type. The Index and IndexDistance types should be Int.

I always see tuples as some kind of anonymous struct, and those two have much more in common than with collections — so when tuples conform to collection, structs and classes should conform as well.
I'm just not sure what to think about that idea (might be useful, but could be terrible)

3. Conform same-type tuples to MutableCollection: If all elements are the same type, you can also modify the values. (If their types vary in any way, however, it would not be safe to allow mutations, since you could assign the wrong type to an element.)

I think that's to confusing without much benefit in return.

4. Add sugar for a tuple of N identical elements: Probably something like `4 * Int`, but opinions can vary.

[small correction applied]
I strongly dislike all shortcuts that have been proposed, and think they don't pull their own weight.

For me, generics are definitely the best solution — maybe we should just declare literals to be types? :wink:


(Xiaodi Wu) #17

Static-Sized Arrays

My preference would still be to build this from four separate features:

1. Magic tuple conformances: We already want to be able to automatically
conform tuples to protocols like Equatable, Hashable, and Comparable. These
can all be compiler magic; they don't have to be definable in userspace.

2. Conform tuples to Collection: The Element type should be the most
specific common supertype of the tuple's elements. If all the elements are
the same type, it would be that type. The Index and IndexDistance types
should be Int.

3. Conform same-type tuples to MutableCollection: If all elements are the
same type, you can also modify the values. (If their types vary in any way,
however, it would not be safe to allow mutations, since you could assign
the wrong type to an element.)

3. Add sugar for a tuple of N identical elements: Probably something like
`4 * Int`, but opinions can vary.

This solution avoids adding another structural type to the language or
introducing non-type generic parameters. It also addresses other needs: 1
and 2 are desirable features in their own right which address other use
cases in addition to this one. And it's nicely incremental, which is always
a plus.

Exactly this. The whole conversation is wildly out of scope and the
critical technical details about implementation of the feature is either
missing or inaccurate, but I'll chime in for future reference to say that
this particular color of the shed is, in my view, the most congruent with
Swift's direction.

···

On Fri, Jun 2, 2017 at 04:28 Brent Royal-Gordon via swift-evolution < swift-evolution@swift.org> wrote:

On May 28, 2017, at 11:37 PM, Daryle Walker via swift-evolution < > swift-evolution@swift.org> wrote:

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Andrew Trick) #18

+1. Sounds great to me as long as the type system can handle it cleanly.
-Andy

···

On Jun 2, 2017, at 2:20 AM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

On May 28, 2017, at 11:37 PM, Daryle Walker via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Static-Sized Arrays

My preference would still be to build this from four separate features:

1. Magic tuple conformances: We already want to be able to automatically conform tuples to protocols like Equatable, Hashable, and Comparable. These can all be compiler magic; they don't have to be definable in userspace.

2. Conform tuples to Collection: The Element type should be the most specific common supertype of the tuple's elements. If all the elements are the same type, it would be that type. The Index and IndexDistance types should be Int.

3. Conform same-type tuples to MutableCollection: If all elements are the same type, you can also modify the values. (If their types vary in any way, however, it would not be safe to allow mutations, since you could assign the wrong type to an element.)

3. Add sugar for a tuple of N identical elements: Probably something like `4 * Int`, but opinions can vary.

This solution avoids adding another structural type to the language or introducing non-type generic parameters. It also addresses other needs: 1 and 2 are desirable features in their own right which address other use cases in addition to this one. And it's nicely incremental, which is always a plus.

--
Brent Royal-Gordon
Architechies


(David Sweeris) #19

Yeah, a "fixed-size array" is probably the most obvious use-case for allowing literal values to be generic parameters. In fact, at that point, they'd just be another type... no special support needed, other than validating the count when assigning array literals to them.

The syntax that gained some traction last time they were seriously discussed would also be better, IMHO:
let x: [Int x 4]
or maybe it was this?
let x: [Int * 4]
Either way, it's more concise, far easier to read (IMHO), and doesn't need new keyword(s).

- Dave Sweeris

···

On May 29, 2017, at 01:12, Tino Heth via swift-evolution <swift-evolution@swift.org> wrote:

I agree strongly that the syntax looks awkward — imho
var v: Vector<Int, size: 3>
would be a much better fit than
var v array 3 of Int

As much as I want to have "real" arrays, I don't think we should add new keywords for them.


(David Sweeris) #20

Sure, I meant it as an example of how unwieldy large tuples can be. Even medium ones, really. Tuples are great for bundling a few values, but much more than that any they become annoying to work with because there's no easy way to iterate through them. As a more realistic example, what if you want a stack-allocated 256-element buffer (which is a real possibility since statically-allocated C arrays are imported as tuples)? You have to manually keep track of i, because you have to hard-code which element you're addressing ("buf.0", "buf.1", etc), rather than being able to look it up directly from an index variable like, well, an array ("buf[i]").

Plus the fact that they can't conform to protocols really limits their usefulness in filling the role of a "normal" type. For instance, even though you could easily create the normal, 64-bit hash value from an instance of a `(Int32, Int32)` simply by concatenating the two elements' bits, you can't create a `Dictionary<(Int32, Int32), SomeType>` because there's no mechanism to get `(Int, Int)` to conform to Dictionary's `Hashable` requirement.

Could both of these features get added to Tuples? From a technically PoV, sure, and it's been discussed in previous threads. IMHO we'd get more benefit out of adding support for variadic generic parameters and using literal values as generic parameters (both of which have also been previously discussed).

But it's all out of scope until after Swift 4 comes out, because none of this affects ABI or source-code compatibility.

- Dave Sweeris

···

On May 30, 2017, at 03:25, Pavol Vaskovic <pali@pali.sk> wrote:

On Tue, May 30, 2017 at 7:51 AM, David Sweeris <davesweeris@mac.com> wrote:

`(Int, Int, Int, Int)` isn't *that* horrible compared to "[Int x 4]", but would you want to replace "[Int8 x 10000]" with the multipage-long tuple equivalent?

:flushed:
It would be really helpful to my understanding, if you spoke about a practical use case. This reads as a contrived counterexample to me…

If your type really has 10 000 values in it, why does it have to be static, why doesn't normal Array fit the bill?