Checking in; more thoughts on arrays and variadic generics


(Daryle Walker) #1

1. Variadic generics

When I look at SwiftDoc.org <http://swiftdoc.org/>, I see some functions repeated with differing numbers of parameters. This seems like a job for variadic templates^H^H^H^H^H^H^H^H^H generics, like in C++. Fortunately, someone has already wrote about this, at <https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#variadic-generics>. A new idea I came up with is that both homogeneous (the current support) and heterogeneous variadic parameters can appear in function declarations. Each can appear at most once in a declaration. And they can co-exist in the same declaration; there’s no problem unless the two packs are adjacent and at least the (lexically) second one doesn’t mandate a label. In that case, and when the homogenous pack appears second, count from the lexically last argument backwards until an argument cannot be part of the homogeneous type, that’ll be the border. Count the other way when the homogenous pack is first. (It’s possible for one pack to have zero elements.)

2. More on Arrays

Before, I’ve proposed expressions like “4 * Int” for arrays. But, looking back, it’s not very Swift-y. I had problems with some forms of the syntax giving the specification indices in the reverse order of the dereferencing indices. It looks too quick-and-dirty. And I want to have a revolution over arrays from C, like we gave the enum type.

The new inspiration came from me looking at a random blog about Swift (1?) tuples, where a 2-Int tuple can be expressed by “(Int, Int)” and a 12-Int tuple by “(Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)”, I thought the latter was too excessive, and that there should be a way to reduce the repetition. Oh:

  (repeat Int for 12)

I originally had “by” for the second keyword. But why add one keyword and reuse another when I can reuse two instead. Any compile-time positive integer constant can be used. The specification does not add a nested level:

  (Double, repeat AnyObject for 4, Int)

The floating-point object is at “.0”, the class array uses “.1” through “.4”, and the integer uses static index “.5”. [NOTE: Should we change this?] A funny thing happens if you use a label:

  var X: (Int, sample: repeat Double for 5)

We can use “X.sample” as a source/sink for a 5-tuple of Double. It can also serve as a “[Double]”, except any methods that would change the size are banned. Note that since the elements are not nested, “X.sample.2” is illegal. (“X.sample.2” would be “X.3” instead.) [NOTE: Should we change this?]

I originally was going to have multi-dimensions, but I couldn’t nail the syntax down.

  (repeat Int for 4 for 6 for 3)

looks ugly. And I still would had to figure out the storage-nesting and dereferencing orders. So only single-dimensions are supported. Note that you can nest, like in C:

  (repeat (repeat Int for 4) for 6)

but it’ll be weird that although the outer dimension starts its listing first, the number parts are listed in the reverse order of indexing.

This syntax will be the equivalent of C’s quick-and-dirty syntax. The main feature will be arrays that act as FULL VALUE TYPES, like struct and enum:

  “array” IDENTIFIER “:” SHAPE-EXPRESSION [, PROTOCOL-LIST] “{“ DEFINITION-BLOCK “}”

where the shape expression is:

  “repeat” TYPE “for” “(“ INDEX-LIST “)”

where each index is:

  MIN..<MAX or MIN…MAX or TYPE

where the type option is a enum with contiguous and countable cases (so: no attribute cases, no raw type that can’t be Stridable, no repeated case values, and no value gaps). The range index kinds use Int bounds. [NOTE: Even if I expand it to any Stridable, where would I specify the type?] The index specifiers are comma-separated. It is legal to have zero specifiers; such arrays have a singular element.

If multiple index specifiers are used, they are co-equal from the user’s perspective. The storage order can be specified within the definition block by

  “#storagerank” “(“ NUMBER-LIST “)”

The numbers go from 0 to one less than the number of indices and can appear at most once in the list. (Zero-dimension arrays must have an empty storage-rank list.) Omitted numbers are inserted after the explicit ones, in increasing order. The first number of the list represents the index with the largest span of elements between index values; the last number represents the index with in-memory adjacent elements. If omitted altogether, it defaults to implementation-defined. [NOTE: Should it be 0, 1,…, INDEX-COUNT-MINUS-1 instead?]

The included members should at least be:

  * withUnsafeBufferPointer, like Array
  * withUnsafeMutableBufferPointer, like Array
  * a default initializer, if the element type has one
  * an initializer that takes a block with INDEX-COUNT arguments and a ELEMENT-TYPE return type, for other initialization
  * a subscript that takes the index types in order (will be empty for zero-dimensional arrays)
  * a subscript that takes a tuple of all the indices (in order) [NOTE: Should we have this?]
  * some sort of apply function that goes over all the elements, includes the index coordinates as parameters to the block
  * another apply function that can mutate the elements
  * find some way to partially subscript the elements to a smaller array (may need variadic generics first)

···


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


Variadic generics discussion
(Robert Widmann) #2

Some thoughts inline.

1. Variadic generics

When I look at SwiftDoc.org <http://swiftdoc.org/>, I see some functions repeated with differing numbers of parameters. This seems like a job for variadic templates^H^H^H^H^H^H^H^H^H generics, like in C++. Fortunately, someone has already wrote about this, at <https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#variadic-generics>. A new idea I came up with is that both homogeneous (the current support) and heterogeneous variadic parameters can appear in function declarations. Each can appear at most once in a declaration. And they can co-exist in the same declaration; there’s no problem unless the two packs are adjacent and at least the (lexically) second one doesn’t mandate a label. In that case, and when the homogenous pack appears second, count from the lexically last argument backwards until an argument cannot be part of the homogeneous type, that’ll be the border. Count the other way when the homogenous pack is first. (It’s possible for one pack to have zero elements.)

C++ has a simpler rule (for once): If you’re going to pack, you have to pack last. This is roughly the rule we have as well for argument lists in functions that don’t have labels - they can have any number of variadic parameters because we can use the argument label to guide the tuple type comparison and disambiguate. Here we lack argument labels (and I’m not sure it’s useful to have them).

As for the distinction between heterogeneous and homogenous lists, I’m not sure it’s a useful thing to have unless you’re trying to roll your own Tuple (which is a thing you can do now with HLists <https://github.com/typelift/Swiftz/blob/master/Sources/HList.swift> anyway). Any type that wishes to take a variadic number of homogeneous type variables is a type that can be parametrized by one type variable and enforce the cardinality invariant elsewhere (see std::initializer_list).

2. More on Arrays

Before, I’ve proposed expressions like “4 * Int” for arrays. But, looking back, it’s not very Swift-y. I had problems with some forms of the syntax giving the specification indices in the reverse order of the dereferencing indices. It looks too quick-and-dirty. And I want to have a revolution over arrays from C, like we gave the enum type.

The new inspiration came from me looking at a random blog about Swift (1?) tuples, where a 2-Int tuple can be expressed by “(Int, Int)” and a 12-Int tuple by “(Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)”, I thought the latter was too excessive, and that there should be a way to reduce the repetition. Oh:

  (repeat Int for 12)

I originally had “by” for the second keyword. But why add one keyword and reuse another when I can reuse two instead. Any compile-time positive integer constant can be used. The specification does not add a nested level:

  (Double, repeat AnyObject for 4, Int)

The floating-point object is at “.0”, the class array uses “.1” through “.4”, and the integer uses static index “.5”. [NOTE: Should we change this?] A funny thing happens if you use a label:

  var X: (Int, sample: repeat Double for 5)

We can use “X.sample” as a source/sink for a 5-tuple of Double. It can also serve as a “[Double]”, except any methods that would change the size are banned. Note that since the elements are not nested, “X.sample.2” is illegal. (“X.sample.2” would be “X.3” instead.) [NOTE: Should we change this?]

I originally was going to have multi-dimensions, but I couldn’t nail the syntax down.

  (repeat Int for 4 for 6 for 3)

looks ugly. And I still would had to figure out the storage-nesting and dereferencing orders. So only single-dimensions are supported. Note that you can nest, like in C:

  (repeat (repeat Int for 4) for 6)

but it’ll be weird that although the outer dimension starts its listing first, the number parts are listed in the reverse order of indexing.

This syntax will be the equivalent of C’s quick-and-dirty syntax. The main feature will be arrays that act as FULL VALUE TYPES, like struct and enum:

  “array” IDENTIFIER “:” SHAPE-EXPRESSION [, PROTOCOL-LIST] “{“ DEFINITION-BLOCK “}”

where the shape expression is:

  “repeat” TYPE “for” “(“ INDEX-LIST “)”

where each index is:

  MIN..<MAX or MIN…MAX or TYPE

where the type option is a enum with contiguous and countable cases (so: no attribute cases, no raw type that can’t be Stridable, no repeated case values, and no value gaps). The range index kinds use Int bounds. [NOTE: Even if I expand it to any Stridable, where would I specify the type?] The index specifiers are comma-separated. It is legal to have zero specifiers; such arrays have a singular element.

If multiple index specifiers are used, they are co-equal from the user’s perspective. The storage order can be specified within the definition block by

  “#storagerank” “(“ NUMBER-LIST “)”

The numbers go from 0 to one less than the number of indices and can appear at most once in the list. (Zero-dimension arrays must have an empty storage-rank list.) Omitted numbers are inserted after the explicit ones, in increasing order. The first number of the list represents the index with the largest span of elements between index values; the last number represents the index with in-memory adjacent elements. If omitted altogether, it defaults to implementation-defined. [NOTE: Should it be 0, 1,…, INDEX-COUNT-MINUS-1 instead?]

This also doesn’t seem to fit with the rest of the language. To my mind a more correct answer is, once again C++-style, integers-in-parameter-position and a catch-all homogenous `Tuple<T, .UInt>` (more than likely, magic type alias).

···

On Jan 21, 2017, at 11:06 AM, Daryle Walker via swift-evolution <swift-evolution@swift.org> wrote:

The included members should at least be:

  * withUnsafeBufferPointer, like Array
  * withUnsafeMutableBufferPointer, like Array
  * a default initializer, if the element type has one
  * an initializer that takes a block with INDEX-COUNT arguments and a ELEMENT-TYPE return type, for other initialization
  * a subscript that takes the index types in order (will be empty for zero-dimensional arrays)
  * a subscript that takes a tuple of all the indices (in order) [NOTE: Should we have this?]
  * some sort of apply function that goes over all the elements, includes the index coordinates as parameters to the block
  * another apply function that can mutate the elements
  * find some way to partially subscript the elements to a smaller array (may need variadic generics first)


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


(Karl) #3

So, 2 quick points:

1) I have often wanted a shorthand for expressing long tuples; I definitely think that’s something worth bike-shedding, e.g. - (String * 4, Int32 * 4) or something
2) Having a special non-growing array type which is called “array” and separate from Array<T> is not such a good idea IMO. I would rather allow tuples to conform to protocols (see: https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#extensions-of-structural-types).

If tuples could conform to protocols, we could say “any tuple of homogenous elements is a Collection”. There would be benefits for the standard library, too - EmptyCollection<T> would disappear, replaced with the empty tuple (), as would CollectionOfOne<T>, to be replaced by a single-element tuple (T). We would also be able to remove our limited-arity == overloads in favour of actual, honest-to-goodness Equatable conformance.

- Karl
<https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#extensions-of-structural-types>

···

On 21 Jan 2017, at 17:06, Daryle Walker via swift-evolution <swift-evolution@swift.org> wrote:

1. Variadic generics

When I look at SwiftDoc.org <http://swiftdoc.org/>, I see some functions repeated with differing numbers of parameters. This seems like a job for variadic templates^H^H^H^H^H^H^H^H^H generics, like in C++. Fortunately, someone has already wrote about this, at <https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#variadic-generics>. A new idea I came up with is that both homogeneous (the current support) and heterogeneous variadic parameters can appear in function declarations. Each can appear at most once in a declaration. And they can co-exist in the same declaration; there’s no problem unless the two packs are adjacent and at least the (lexically) second one doesn’t mandate a label. In that case, and when the homogenous pack appears second, count from the lexically last argument backwards until an argument cannot be part of the homogeneous type, that’ll be the border. Count the other way when the homogenous pack is first. (It’s possible for one pack to have zero elements.)

2. More on Arrays

Before, I’ve proposed expressions like “4 * Int” for arrays. But, looking back, it’s not very Swift-y. I had problems with some forms of the syntax giving the specification indices in the reverse order of the dereferencing indices. It looks too quick-and-dirty. And I want to have a revolution over arrays from C, like we gave the enum type.

The new inspiration came from me looking at a random blog about Swift (1?) tuples, where a 2-Int tuple can be expressed by “(Int, Int)” and a 12-Int tuple by “(Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)”, I thought the latter was too excessive, and that there should be a way to reduce the repetition. Oh:

  (repeat Int for 12)

I originally had “by” for the second keyword. But why add one keyword and reuse another when I can reuse two instead. Any compile-time positive integer constant can be used. The specification does not add a nested level:

  (Double, repeat AnyObject for 4, Int)

The floating-point object is at “.0”, the class array uses “.1” through “.4”, and the integer uses static index “.5”. [NOTE: Should we change this?] A funny thing happens if you use a label:

  var X: (Int, sample: repeat Double for 5)

We can use “X.sample” as a source/sink for a 5-tuple of Double. It can also serve as a “[Double]”, except any methods that would change the size are banned. Note that since the elements are not nested, “X.sample.2” is illegal. (“X.sample.2” would be “X.3” instead.) [NOTE: Should we change this?]

I originally was going to have multi-dimensions, but I couldn’t nail the syntax down.

  (repeat Int for 4 for 6 for 3)

looks ugly. And I still would had to figure out the storage-nesting and dereferencing orders. So only single-dimensions are supported. Note that you can nest, like in C:

  (repeat (repeat Int for 4) for 6)

but it’ll be weird that although the outer dimension starts its listing first, the number parts are listed in the reverse order of indexing.

This syntax will be the equivalent of C’s quick-and-dirty syntax. The main feature will be arrays that act as FULL VALUE TYPES, like struct and enum:

  “array” IDENTIFIER “:” SHAPE-EXPRESSION [, PROTOCOL-LIST] “{“ DEFINITION-BLOCK “}”

where the shape expression is:

  “repeat” TYPE “for” “(“ INDEX-LIST “)”

where each index is:

  MIN..<MAX or MIN…MAX or TYPE

where the type option is a enum with contiguous and countable cases (so: no attribute cases, no raw type that can’t be Stridable, no repeated case values, and no value gaps). The range index kinds use Int bounds. [NOTE: Even if I expand it to any Stridable, where would I specify the type?] The index specifiers are comma-separated. It is legal to have zero specifiers; such arrays have a singular element.

If multiple index specifiers are used, they are co-equal from the user’s perspective. The storage order can be specified within the definition block by

  “#storagerank” “(“ NUMBER-LIST “)”

The numbers go from 0 to one less than the number of indices and can appear at most once in the list. (Zero-dimension arrays must have an empty storage-rank list.) Omitted numbers are inserted after the explicit ones, in increasing order. The first number of the list represents the index with the largest span of elements between index values; the last number represents the index with in-memory adjacent elements. If omitted altogether, it defaults to implementation-defined. [NOTE: Should it be 0, 1,…, INDEX-COUNT-MINUS-1 instead?]

The included members should at least be:

  * withUnsafeBufferPointer, like Array
  * withUnsafeMutableBufferPointer, like Array
  * a default initializer, if the element type has one
  * an initializer that takes a block with INDEX-COUNT arguments and a ELEMENT-TYPE return type, for other initialization
  * a subscript that takes the index types in order (will be empty for zero-dimensional arrays)
  * a subscript that takes a tuple of all the indices (in order) [NOTE: Should we have this?]
  * some sort of apply function that goes over all the elements, includes the index coordinates as parameters to the block
  * another apply function that can mutate the elements
  * find some way to partially subscript the elements to a smaller array (may need variadic generics first)


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


(Tino) #4

2. More on Arrays

Before, I’ve proposed expressions like “4 * Int” for arrays. But, looking back, it’s not very Swift-y. I had problems with some forms of the syntax giving the specification indices in the reverse order of the dereferencing indices. It looks too quick-and-dirty. And I want to have a revolution over arrays from C, like we gave the enum type.

The new inspiration came from me looking at a random blog about Swift (1?) tuples, where a 2-Int tuple can be expressed by “(Int, Int)” and a 12-Int tuple by “(Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)”, I thought the latter was too excessive, and that there should be a way to reduce the repetition. Oh:

  (repeat Int for 12)

I remember that discussion and I was happy that despite an enthusiastic start, it went nowhere:
Imho translating fixed-size arrays to tuples is just a hack that should be fixed, not extended.

Arrays are one of the most basic concepts in programming, so I don't want to mix them with tuples - especially as there is a clean alternative (there are still some pieces missing, but "Vector<type: Int, size: 12>" looks much better than any tuple-magic to me).


(Daryle Walker) #5

Some thoughts inline.

1. Variadic generics

When I look at SwiftDoc.org <http://swiftdoc.org/>, I see some functions repeated with differing numbers of parameters. This seems like a job for variadic templates^H^H^H^H^H^H^H^H^H generics, like in C++. Fortunately, someone has already wrote about this, at <https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#variadic-generics>. A new idea I came up with is that both homogeneous (the current support) and heterogeneous variadic parameters can appear in function declarations. Each can appear at most once in a declaration. And they can co-exist in the same declaration; there’s no problem unless the two packs are adjacent and at least the (lexically) second one doesn’t mandate a label. In that case, and when the homogenous pack appears second, count from the lexically last argument backwards until an argument cannot be part of the homogeneous type, that’ll be the border. Count the other way when the homogenous pack is first. (It’s possible for one pack to have zero elements.)

C++ has a simpler rule (for once): If you’re going to pack, you have to pack last. This is roughly the rule we have as well for argument lists in functions that don’t have labels - they can have any number of variadic parameters because we can use the argument label to guide the tuple type comparison and disambiguate. Here we lack argument labels (and I’m not sure it’s useful to have them).

As for the distinction between heterogeneous and homogenous lists, I’m not sure it’s a useful thing to have unless you’re trying to roll your own Tuple (which is a thing you can do now with HLists <https://github.com/typelift/Swiftz/blob/master/Sources/HList.swift> anyway). Any type that wishes to take a variadic number of homogeneous type variables is a type that can be parametrized by one type variable and enforce the cardinality invariant elsewhere (see std::initializer_list).

The “homogenous list” I’m talking about are the variadic parameters that are already in the language. And they can already be non-last in the list. (At least it compiled, but the parameter I had after it had a default value, so I don’t know if that made a difference.) The “homogenous” is to differentiate them from parameters introduced by variadic generics (which would be “heterogeneous”).

My inspiration was from C++, where a function template can have both C++ and C variadic parameters. The C-level ones have to be last, and the C++-level ones next-to-last. Last I checked, this pattern was only useful for stupid resolution tricks; you couldn’t actually use such a function. (Using C-level variadics requires using a macro on the preceding argument. But on a dual-variadic function, this would be the C++-level variadic argument (instead of a singular one), which was (is?) not compatible with said macro.) I just want to make a generic function that uses both kinds of variadic argument lists actually usable, by defining the dividing line. Again, this is only a problem if the two lists are adjacent and the second list doesn’t have an external label.

AFAIK, there is no concrete specification for variadic generic functions yet. Hopefully, that team will take my advice into account. IF you don’t think a heterogeneous list should be non-last, nor co-exist with homogenous list, you can bring it up then.

2. More on Arrays

[SNIP]

This also doesn’t seem to fit with the rest of the language. To my mind a more correct answer is, once again C++-style, integers-in-parameter-position and a catch-all homogenous `Tuple<T, .UInt>` (more than likely, magic type alias).

Right now, generics can’t use non-type parameters, so any “FixedArray<Int, 6>”-like syntax is out. I feel that anonymous arrays would have a usage model mostly like tuples; hence the close syntax. I prefer a built-in type instead of a library type since we need some magic to enforce:

  strideof( ArrayType ) == Element-Count * strideof( ElementType )

down to the extent that there shouldn't be any padding between elements of the inner non-array type (in the case of nested arrays).

Since we anonymous and named heterogenous product types (tuples vs. structs and classes), I want the same thing for homogenous product types (array extension to tuples vs. nominal “array" type). We broke away from C in the handling of enums, why not arrays too?

···

On Jan 23, 2017, at 3:33 PM, Robert Widmann <devteam.codafi@gmail.com> wrote:

On Jan 21, 2017, at 11:06 AM, Daryle Walker via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:


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


(Slava Pestov) #6

So, 2 quick points:

1) I have often wanted a shorthand for expressing long tuples; I definitely think that’s something worth bike-shedding, e.g. - (String * 4, Int32 * 4) or something

Why not define a struct, or a tuple consisting of two arrays?

2) Having a special non-growing array type which is called “array” and separate from Array<T> is not such a good idea IMO. I would rather allow tuples to conform to protocols (see: https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#extensions-of-structural-types).

If tuples could conform to protocols, we could say “any tuple of homogenous elements is a Collection”. There would be benefits for the standard library, too - EmptyCollection<T> would disappear, replaced with the empty tuple (),

This sounds too clever.

as would CollectionOfOne<T>, to be replaced by a single-element tuple (T).

For what it’s worth, Swift doesn’t have single-element tuples. (T) is just sugar for the type T itself.

We would also be able to remove our limited-arity == overloads in favour of actual, honest-to-goodness Equatable conformance.

I like this idea though.

···

On Jan 27, 2017, at 11:44 AM, Karl Wagner via swift-evolution <swift-evolution@swift.org> wrote:

- Karl
<https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#extensions-of-structural-types>_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Karl) #7

So, 2 quick points:

1) I have often wanted a shorthand for expressing long tuples; I definitely think that’s something worth bike-shedding, e.g. - (String * 4, Int32 * 4) or something

Why not define a struct, or a tuple consisting of two arrays?

Because I want a fixed-length guarantee; ([String], [Int]) could have any number of Strings or Ints.
It’s just a shorthand for defining long or complex tuples; we currently import C arrays as massive tuples which can be basically impossible to read.

2) Having a special non-growing array type which is called “array” and separate from Array<T> is not such a good idea IMO. I would rather allow tuples to conform to protocols (see: https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#extensions-of-structural-types).

If tuples could conform to protocols, we could say “any tuple of homogenous elements is a Collection”. There would be benefits for the standard library, too - EmptyCollection<T> would disappear, replaced with the empty tuple (),

This sounds too clever.

Yeah… the cleverness is one of things I like about it. We get to remove these canned conformances and reduce the stdlib surface area while gaining an efficient way to express a fixed-size Collection. It would come with all kinds of supplementary benefits, such as iterating and mapping the elements of a tuple.

as would CollectionOfOne<T>, to be replaced by a single-element tuple (T).

For what it’s worth, Swift doesn’t have single-element tuples. (T) is just sugar for the type T itself.

Would it be unreasonable to separate those, so that (T) is separate from T instead of being a synonym for it? There is some awkwardness with tuples due to legacy designs. Perhaps this would help clean it up a little (or perhaps make it worse, who knows?)

For source compatibility, we could allow an implicit conversion; in the same way that a T can be implicitly “promoted" to an Optional<T>, it could be implicitly “promoted” to a single-element tuple of T (and vice-versa).

···

On 27 Jan 2017, at 22:25, Slava Pestov <spestov@apple.com> wrote:

On Jan 27, 2017, at 11:44 AM, Karl Wagner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

We would also be able to remove our limited-arity == overloads in favour of actual, honest-to-goodness Equatable conformance.

I like this idea though.

- Karl
<https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#extensions-of-structural-types>_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Daryle Walker) #8

You could use “(repeat T for 1)” if you really wanted. (I wonder if I should allow “(…, repeat T for 0)” to define trailing array segments.)

My concept always had the array directive within a “()”. One, the isolation (hopefully) prevents “repeat” and/or “for” from being parsed for their statement meanings. Two, I think “repeat repeat T for N for M” looks awful compared to “(…, repeat (repeat T for N) for M,… )”.

···

On Jan 27, 2017, at 4:25 PM, Slava Pestov <spestov@apple.com> wrote:

On Jan 27, 2017, at 11:44 AM, Karl Wagner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

as would CollectionOfOne<T>, to be replaced by a single-element tuple (T).

For what it’s worth, Swift doesn’t have single-element tuples. (T) is just sugar for the type T itself.


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


(Daryle Walker) #9

I was thinking that an array would have the same (or bigger) interaction model as a homogenous tuple. Are you thinking some sort of interface a (homogenous) tuple should have but an array shouldn’t?

···

On Jan 30, 2017, at 7:45 AM, Tino Heth <2th@gmx.de> wrote:

Arrays are one of the most basic concepts in programming, so I don't want to mix them with tuples - especially as there is a clean alternative (there are still some pieces missing, but "Vector<type: Int, size: 12>" looks much better than any tuple-magic to me).


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


(Robert Widmann) #10

~Robert Widmann

2017/01/26 13:37、Daryle Walker <darylew@mac.com <mailto:darylew@mac.com>> のメッセージ:

Some thoughts inline.

1. Variadic generics

When I look at SwiftDoc.org <http://swiftdoc.org/>, I see some functions repeated with differing numbers of parameters. This seems like a job for variadic templates^H^H^H^H^H^H^H^H^H generics, like in C++. Fortunately, someone has already wrote about this, at <https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#variadic-generics>. A new idea I came up with is that both homogeneous (the current support) and heterogeneous variadic parameters can appear in function declarations. Each can appear at most once in a declaration. And they can co-exist in the same declaration; there’s no problem unless the two packs are adjacent and at least the (lexically) second one doesn’t mandate a label. In that case, and when the homogenous pack appears second, count from the lexically last argument backwards until an argument cannot be part of the homogeneous type, that’ll be the border. Count the other way when the homogenous pack is first. (It’s possible for one pack to have zero elements.)

C++ has a simpler rule (for once): If you’re going to pack, you have to pack last. This is roughly the rule we have as well for argument lists in functions that don’t have labels - they can have any number of variadic parameters because we can use the argument label to guide the tuple type comparison and disambiguate. Here we lack argument labels (and I’m not sure it’s useful to have them).

As for the distinction between heterogeneous and homogenous lists, I’m not sure it’s a useful thing to have unless you’re trying to roll your own Tuple (which is a thing you can do now with HLists <https://github.com/typelift/Swiftz/blob/master/Sources/HList.swift> anyway). Any type that wishes to take a variadic number of homogeneous type variables is a type that can be parametrized by one type variable and enforce the cardinality invariant elsewhere (see std::initializer_list).

The “homogenous list” I’m talking about are the variadic parameters that are already in the language. And they can already be non-last in the list. (At least it compiled, but the parameter I had after it had a default value, so I don’t know if that made a difference.)

A warning was just introduced if you declare non-terminal variadics without argument labels to disambiguate parameters. That's what I meant. Being able to constrain a signature so that it must have at least a certain cardinality seems useful, but what I’m saying is I’m not sure it’s useful to constrain it in multiple directions. Consider

struct Foo<T, U, V…, LAST> {}

which can be instantiated as Foo<String, Int, UInt, NSObject> to satisfy the requirements of the signature. Rearranging this declaration to shuffle non-variadic parameters to the front doesn’t change that

struct Foo<T, U, LAST, V...> {}

It just changes the order in which you or the typechecker comes around to instantiate the signature. We’re getting down to matters of practicality here: Yes, there’s nothing stopping us from allowing variadics in any position, but what’s the point? What kinds of structures can I only model with this kind of bi-directional cardinality matching?

The “homogenous” is to differentiate them from parameters introduced by variadic generics (which would be “heterogeneous”).

Ah, so there’s not a hard difference here just a terminology change. The use of “homogenous” and “heterogeneous” here is superfluous, since it’s pretty much assumed that in a variadic setting you wish for a heterogeneous list of types. After all, by linearity, Foo<T, T, T, T…> is isomorphic to Foo<T>.

My inspiration was from C++, where a function template can have both C++ and C variadic parameters. The C-level ones have to be last, and the C++-level ones next-to-last. Last I checked, this pattern was only useful for stupid resolution tricks; you couldn’t actually use such a function. (Using C-level variadics requires using a macro on the preceding argument. But on a dual-variadic function, this would be the C++-level variadic argument (instead of a singular one), which was (is?) not compatible with said macro.) I just want to make a generic function that uses both kinds of variadic argument lists actually usable, by defining the dividing line. Again, this is only a problem if the two lists are adjacent and the second list doesn’t have an external label.

AFAIK, there is no concrete specification for variadic generic functions yet. Hopefully, that team will take my advice into account. IF you don’t think a heterogeneous list should be non-last, nor co-exist with homogenous list, you can bring it up then.

2. More on Arrays

[SNIP]

This also doesn’t seem to fit with the rest of the language. To my mind a more correct answer is, once again C++-style, integers-in-parameter-position and a catch-all homogenous `Tuple<T, .UInt>` (more than likely, magic type alias).

Right now, generics can’t use non-type parameters, so any “FixedArray<Int, 6>”-like syntax is out. I feel that anonymous arrays would have a usage model mostly like tuples; hence the close syntax. I prefer a built-in type instead of a library type since we need some magic to enforce:

As do I, but because we do not limit the cardinality of tuples (or "fixed arrays") it is, by definition, impossible to declare every possible instance of the type in stdlib without compiler magic. The alternative is to declare bankruptcy at some arbitrary number where the compiler or sanity of stdlib authors breaks down (à la Haskell or Scala).

And as long as we're spitballing, no syntax is "out" just cosmetically appealing/unappealing :wink:

  strideof( ArrayType ) == Element-Count * strideof( ElementType )

down to the extent that there shouldn't be any padding between elements of the inner non-array type (in the case of nested arrays).

Smells like a static ContiguousArray.

Since we anonymous and named heterogenous product types (tuples vs. structs and classes), I want the same thing for homogenous product types (array extension to tuples vs. nominal “array" type). We broke away from C in the handling of enums, why not arrays too?

We do break with C by importing arrays as (occasionally massive) tuples. Still, you're absolutely right, we can do so much better here.

···

On Jan 23, 2017, at 3:33 PM, Robert Widmann <devteam.codafi@gmail.com <mailto:devteam.codafi@gmail.com>> wrote:

On Jan 21, 2017, at 11:06 AM, Daryle Walker via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:


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


(Xiaodi Wu) #11

So, 2 quick points:

1) I have often wanted a shorthand for expressing long tuples; I
definitely think that’s something worth bike-shedding, e.g. - (String *
4, Int32 * 4) or something

Why not define a struct, or a tuple consisting of two arrays?

Because I want a fixed-length guarantee; ([String], [Int]) could have any
number of Strings or Ints.
It’s just a shorthand for defining long or complex tuples; we currently
import C arrays as massive tuples which can be basically impossible to read.

2) Having a special non-growing array type which is called “array” and
separate from Array<T> is not such a good idea IMO. I would rather allow
tuples to conform to protocols (see: https://github.com/
apple/swift/blob/master/docs/GenericsManifesto.md#
extensions-of-structural-types).

If tuples could conform to protocols, we could say “any tuple of
homogenous elements is a Collection”. There would be benefits for the
standard library, too - EmptyCollection<T> would disappear, replaced with
the empty tuple (),

This sounds too clever.

Yeah… the cleverness is one of things I *like* about it. We get to remove
these canned conformances and reduce the stdlib surface area while gaining
an efficient way to express a fixed-size Collection. It would come with all
kinds of supplementary benefits, such as iterating and mapping the elements
of a tuple.

as would CollectionOfOne<T>, to be replaced by a single-element tuple (T).

For what it’s worth, Swift doesn’t have single-element tuples. (T) is just
sugar for the type T itself.

Would it be unreasonable to separate those, so that (T) is separate from T
instead of being a synonym for it? There is some awkwardness with tuples
due to legacy designs. Perhaps this would help clean it up a little (or
perhaps make it worse, who knows?)

For source compatibility, we could allow an implicit conversion; in the
same way that a T can be implicitly “promoted" to an Optional<T>, it could
be implicitly “promoted” to a single-element tuple of T (and vice-versa).

What's the use case for this source-breaking change? Is
`CollectionOfOne<T>` deficient in some way?

···

On Fri, Jan 27, 2017 at 6:55 PM, Karl Wagner via swift-evolution < swift-evolution@swift.org> wrote:

On 27 Jan 2017, at 22:25, Slava Pestov <spestov@apple.com> wrote:
On Jan 27, 2017, at 11:44 AM, Karl Wagner via swift-evolution < > swift-evolution@swift.org> wrote:

We would also be able to remove our limited-arity == overloads in favour
of actual, honest-to-goodness Equatable conformance.

I like this idea though.

- Karl

<https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#extensions-of-structural-types>
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

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


(Slava Pestov) #12

So, 2 quick points:

1) I have often wanted a shorthand for expressing long tuples; I definitely think that’s something worth bike-shedding, e.g. - (String * 4, Int32 * 4) or something

Why not define a struct, or a tuple consisting of two arrays?

Because I want a fixed-length guarantee; ([String], [Int]) could have any number of Strings or Ints.

Ok, maybe a struct would named fields would be better though.

It’s just a shorthand for defining long or complex tuples; we currently import C arrays as massive tuples which can be basically impossible to read.

I agree that this is a problem — fixed length arrays should be imported better, once we have the right language features.

2) Having a special non-growing array type which is called “array” and separate from Array<T> is not such a good idea IMO. I would rather allow tuples to conform to protocols (see: https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#extensions-of-structural-types).

If tuples could conform to protocols, we could say “any tuple of homogenous elements is a Collection”. There would be benefits for the standard library, too - EmptyCollection<T> would disappear, replaced with the empty tuple (),

This sounds too clever.

Yeah… the cleverness is one of things I like about it. We get to remove these canned conformances and reduce the stdlib surface area while gaining an efficient way to express a fixed-size Collection. It would come with all kinds of supplementary benefits, such as iterating and mapping the elements of a tuple.

as would CollectionOfOne<T>, to be replaced by a single-element tuple (T).

For what it’s worth, Swift doesn’t have single-element tuples. (T) is just sugar for the type T itself.

Would it be unreasonable to separate those, so that (T) is separate from T instead of being a synonym for it? There is some awkwardness with tuples due to legacy designs. Perhaps this would help clean it up a little (or perhaps make it worse, who knows?)

For source compatibility, we could allow an implicit conversion; in the same way that a T can be implicitly “promoted" to an Optional<T>, it could be implicitly “promoted” to a single-element tuple of T (and vice-versa).

Sure, we *could* re-design tuple types in a way where single element tuples make sense. Then we’d have to come up with a source compatibility story for Swift 3 vs Swift 4, fix any fallout (compiler crashes) from this change, implement migrator support when the stdlib is changed to use the new features, etc. But think of it this way — every such “unnecessary” change (and I realize this is subjective!) is taking away cycles the team could use to fix crashes, improve compiler speed, and improve diagnostics. Not to mention implementing the other evolution proposals which arguably increase expressive power in important ways we feel we need for ABI stability, such as generic subscripts.

···

On Jan 27, 2017, at 4:55 PM, Karl Wagner <razielim@gmail.com> wrote:

On 27 Jan 2017, at 22:25, Slava Pestov <spestov@apple.com <mailto:spestov@apple.com>> wrote:

On Jan 27, 2017, at 11:44 AM, Karl Wagner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

We would also be able to remove our limited-arity == overloads in favour of actual, honest-to-goodness Equatable conformance.

I like this idea though.

- Karl
<https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#extensions-of-structural-types>_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Daryle Walker) #13

~Robert Widmann

2017/01/26 13:37、Daryle Walker <darylew@mac.com <mailto:darylew@mac.com>> のメッセージ:

Some thoughts inline.

1. Variadic generics

When I look at SwiftDoc.org <http://swiftdoc.org/>, I see some functions repeated with differing numbers of parameters. This seems like a job for variadic templates^H^H^H^H^H^H^H^H^H generics, like in C++. Fortunately, someone has already wrote about this, at <https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#variadic-generics>. A new idea I came up with is that both homogeneous (the current support) and heterogeneous variadic parameters can appear in function declarations. Each can appear at most once in a declaration. And they can co-exist in the same declaration; there’s no problem unless the two packs are adjacent and at least the (lexically) second one doesn’t mandate a label. In that case, and when the homogenous pack appears second, count from the lexically last argument backwards until an argument cannot be part of the homogeneous type, that’ll be the border. Count the other way when the homogenous pack is first. (It’s possible for one pack to have zero elements.)

C++ has a simpler rule (for once): If you’re going to pack, you have to pack last. This is roughly the rule we have as well for argument lists in functions that don’t have labels - they can have any number of variadic parameters because we can use the argument label to guide the tuple type comparison and disambiguate. Here we lack argument labels (and I’m not sure it’s useful to have them).

As for the distinction between heterogeneous and homogenous lists, I’m not sure it’s a useful thing to have unless you’re trying to roll your own Tuple (which is a thing you can do now with HLists <https://github.com/typelift/Swiftz/blob/master/Sources/HList.swift> anyway). Any type that wishes to take a variadic number of homogeneous type variables is a type that can be parametrized by one type variable and enforce the cardinality invariant elsewhere (see std::initializer_list).

The “homogenous list” I’m talking about are the variadic parameters that are already in the language. And they can already be non-last in the list. (At least it compiled, but the parameter I had after it had a default value, so I don’t know if that made a difference.)

A warning was just introduced if you declare non-terminal variadics without argument labels to disambiguate parameters. That's what I meant. Being able to constrain a signature so that it must have at least a certain cardinality seems useful, but what I’m saying is I’m not sure it’s useful to constrain it in multiple directions. Consider

Just checked with a Playground; it’s actually an error. But seeing this:

struct Foo<T, U, V…, LAST> {}

which can be instantiated as Foo<String, Int, UInt, NSObject> to satisfy the requirements of the signature. Rearranging this declaration to shuffle non-variadic parameters to the front doesn’t change that

struct Foo<T, U, LAST, V...> {}

It just changes the order in which you or the typechecker comes around to instantiate the signature. We’re getting down to matters of practicality here: Yes, there’s nothing stopping us from allowing variadics in any position, but what’s the point? What kinds of structures can I only model with this kind of bi-directional cardinality matching?

I’m not writing about variadic parameters within “<>”…

The “homogenous” is to differentiate them from parameters introduced by variadic generics (which would be “heterogeneous”).

Ah, so there’s not a hard difference here just a terminology change. The use of “homogenous” and “heterogeneous” here is superfluous, since it’s pretty much assumed that in a variadic setting you wish for a heterogeneous list of types. After all, by linearity, Foo<T, T, T, T…> is isomorphic to Foo<T>.

…but within the “()”. I think we’ve been writing at cross-purposes. I’m talking about functions like:

func test1<...T>(myIntegers: Int…, _ myT: T…)

or

func test2<...T>(_ myT: T…, _ integers Int…)

and finding the line between the “Int” list (homogenous and “()”-level) and “T” list (heterogenous and originally “<>”-level). But now I know these cases are already banned, so don’t worry about it.

I wasn't considering homogenous arguments within “<>” (assuming variadic generics are added), since those would be a case within heterogenous arguments, as you said.

···

On Jan 27, 2017, at 11:55 AM, Robert Widmann <devteam.codafi@gmail.com> wrote:

On Jan 23, 2017, at 3:33 PM, Robert Widmann <devteam.codafi@gmail.com <mailto:devteam.codafi@gmail.com>> wrote:

On Jan 21, 2017, at 11:06 AM, Daryle Walker via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:


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


(Daryle Walker) #14

More like ContiguousArray is a non-static version of the decades-old classic array concept. An array in a code-block scope shouldn’t have to implement its data in free-store if its size (and shape) can be defined at compile-time.

···

On Jan 27, 2017, at 11:55 AM, Robert Widmann <devteam.codafi@gmail.com> wrote:

~Robert Widmann

2017/01/26 13:37、Daryle Walker <darylew@mac.com <mailto:darylew@mac.com>> のメッセージ:

  strideof( ArrayType ) == Element-Count * strideof( ElementType )

down to the extent that there shouldn't be any padding between elements of the inner non-array type (in the case of nested arrays).

Smells like a static ContiguousArray.


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


(Karl) #15

I appreciate that, but I don’t think this is a small feature. Fixed-size arrays (or, more generally, fixed-size collections) are an important hole in the standard library. I think it’s a given that we want to approach that one day.

At the same time, we already _have_ tuples. They use these freestanding limited-arity == operators rather than an actual Equatable conformance, so they can’t be used as Equatables by generic code. If we did improve that, the same mechanism (whatever it is) would like have all the bolts in place for a Collection conformance, too - and that’s great, since conceptually a tuple is a group of things. There would, of course, be an implementation cost - but likely less than what the OP suggested (with a whole new “array” declaration).

Consider even if we had compile-time constants like Vector<T, size: Int> — how would that be implemented? What would its backing-type be? It would probably want to use that constant to create a fixed-length tuple; again, there would be less implementation effort by just having the tuple conform to Collection directly, instead.

Single-element tuples aren’t necessary for Collection conformance, anyway. It would just make things _even_ neater by allowing us to remove a standard library type.

- Karl

···

On 29 Jan 2017, at 00:16, Slava Pestov <spestov@apple.com> wrote:

On Jan 27, 2017, at 4:55 PM, Karl Wagner <razielim@gmail.com <mailto:razielim@gmail.com>> wrote:

On 27 Jan 2017, at 22:25, Slava Pestov <spestov@apple.com <mailto:spestov@apple.com>> wrote:

On Jan 27, 2017, at 11:44 AM, Karl Wagner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

So, 2 quick points:

1) I have often wanted a shorthand for expressing long tuples; I definitely think that’s something worth bike-shedding, e.g. - (String * 4, Int32 * 4) or something

Why not define a struct, or a tuple consisting of two arrays?

Because I want a fixed-length guarantee; ([String], [Int]) could have any number of Strings or Ints.

Ok, maybe a struct would named fields would be better though.

It’s just a shorthand for defining long or complex tuples; we currently import C arrays as massive tuples which can be basically impossible to read.

I agree that this is a problem — fixed length arrays should be imported better, once we have the right language features.

2) Having a special non-growing array type which is called “array” and separate from Array<T> is not such a good idea IMO. I would rather allow tuples to conform to protocols (see: https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#extensions-of-structural-types).

If tuples could conform to protocols, we could say “any tuple of homogenous elements is a Collection”. There would be benefits for the standard library, too - EmptyCollection<T> would disappear, replaced with the empty tuple (),

This sounds too clever.

Yeah… the cleverness is one of things I like about it. We get to remove these canned conformances and reduce the stdlib surface area while gaining an efficient way to express a fixed-size Collection. It would come with all kinds of supplementary benefits, such as iterating and mapping the elements of a tuple.

as would CollectionOfOne<T>, to be replaced by a single-element tuple (T).

For what it’s worth, Swift doesn’t have single-element tuples. (T) is just sugar for the type T itself.

Would it be unreasonable to separate those, so that (T) is separate from T instead of being a synonym for it? There is some awkwardness with tuples due to legacy designs. Perhaps this would help clean it up a little (or perhaps make it worse, who knows?)

For source compatibility, we could allow an implicit conversion; in the same way that a T can be implicitly “promoted" to an Optional<T>, it could be implicitly “promoted” to a single-element tuple of T (and vice-versa).

Sure, we *could* re-design tuple types in a way where single element tuples make sense. Then we’d have to come up with a source compatibility story for Swift 3 vs Swift 4, fix any fallout (compiler crashes) from this change, implement migrator support when the stdlib is changed to use the new features, etc. But think of it this way — every such “unnecessary” change (and I realize this is subjective!) is taking away cycles the team could use to fix crashes, improve compiler speed, and improve diagnostics. Not to mention implementing the other evolution proposals which arguably increase expressive power in important ways we feel we need for ABI stability, such as generic subscripts.

We would also be able to remove our limited-arity == overloads in favour of actual, honest-to-goodness Equatable conformance.

I like this idea though.

- Karl
<https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#extensions-of-structural-types>_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Tino) #16

Consider even if we had compile-time constants like Vector<T, size: Int> — how would that be implemented? What would its backing-type be?

Imho it's very simple — UnsafeMutableBufferPointer would be an obvious choice.

It would probably want to use that constant to create a fixed-length tuple;

No, it would use that constant to create a fixed-size array of bytes. Don't forget that everything boils down to arrays of bytes (tuples included).

again, there would be less implementation effort by just having the tuple conform to Collection directly, instead.

Well, it's already trivial to build Vector4<T> instead of Vector<T, size: 4> using current Swift, so imho the step shouldn't be big — and it is obvious to interpret the meaning of this small syntax addition.
Additionally, such an extension of generics would be useful for other things as well.

On the other hand, I think the change you suggest is rather fundamental:
It requires a syntax which is completely new (without an obvious choice on how that syntax should look), and I see no reason for Swift to take a special path.
Most languages I know have a clean distinction between records/structs/tuples and arrays, and this makes sense.
Iterating over the elements in a tuple is like iterating over the properties in a struct or class, and (metaprogramming aside) that feels very strange to me.

- Tino


(Anton Zhilin) #17

And then we just have an Array<T> with static size guarantee. But to solve
the import story and gain space for optimization, we need stack-storage
arrays.

···

2017-02-06 15:35 GMT+03:00 Tino Heth via swift-evolution < swift-evolution@swift.org>:

Consider even if we had compile-time constants like Vector<T, size: Int> —
how would that be implemented? What would its backing-type be?

Imho it's very simple — UnsafeMutableBufferPointer would be an obvious
choice.