Tuple of value pack expansion

co-author: Holly Borla

Introduction

Building upon the Value and Type Parameter Packs proposal SE-0393, this proposal enables referencing a tuple value that contains a value pack inside a pack repetition pattern.

Motivation

When a tuple value contains a value pack, there is no way to reference those pack elements or pass them as a value pack function argument.

Additionally, type parameter packs are only permitted within a function parameter list, tuple element, or generic argument list. This precludes declaring a type parameter pack as a function return type, type alias, or as a local variable type to permit storage. The available solution to these restrictions is to contain the value pack in a tuple, which makes it important to provide full functional parity between value packs and tuple values containing them.

Proposed solution

This proposal extends the functionality of pack repetition patterns to values of abstract tuple type, which enables an implicit conversion of an abstract tuple value to its contained value pack. An abstract tuple type is a tuple that has an unknown length and elements of unknown types. Its elements are that of a single type parameter pack and no additional elements, and no label. In other words, the elements of the type parameter pack are the elements of the tuple. An abstract tuple value is a value of abstract tuple type. This proposal provides methods to individually access the dynamic pack elements of an abstract tuple value inside of a repetition pattern.

Distinction between tuple values and value packs

The following example demonstrates a pack repetition pattern on a value pack and an abstract tuple value separately first, then together but with the repetition pattern operating only on the value pack, and finally with the repetition pattern operating on both the value pack and the tuple value's contained value pack together interleaved.

func example<each U>(packElements value: repeat each U, tuple: (repeat each U)) {
  print((repeat each value))
  print((repeat each tuple))
  
  print((repeat (each value, tuple)))
  
  print((repeat (each value, each tuple)))
}

example(packElements: 1, 2, 3, tuple: (4, 5, 6))

// Prints the following output:
// (1, 2, 3)
// (4, 5, 6)
// ((1, (4, 5, 6)), (2, (4, 5, 6)), (3, (4, 5, 6)))
// ((1, 4), (2, 5), (3, 6))

Detailed design

Pack reference expressions inside a repetition pattern can have abstract tuple type. The outer structure of the tuple is removed, leaving the elements of a value pack:

func expand<each T>(value: (repeat each T)) -> (repeat (each T)?) {
  return (repeat each value)
}

Applying the pack repetition pattern effectively removes the outer structure of the tuple leaving just the value pack. In the repetition expression, the base tuple is evaluated once before iterating over its elements.

repeat each <tuple expression>

// the above is evaluated like this
let tempTuple = <tuple expression>
repeat each tempTuple

Source compatibility

There is no source compatibility impact given that this is an additive change. It enables compiling code that previously would not compile.

ABI compatibility

This proposal does not add or affect ABI as its impact is only on expressions. It does not change external declarations or types. It rests atop the ABI introduced in the Value and Type Parameter Packs proposal.

Implications on adoption

Given that this change rests atop the ABI introduced in the Value and Type Parameter Packs proposal, this shares with it the same runtime back-deployment story.

Alternatives considered

The Value and Type Parameter Packs proposal provides no means for declaring local variable value packs or stored property packs. This is mentioned as a potential future direction, but without that, containing the packs within tuples is the way to access such functionality. Therefore an alternative to this proposal would be to instead pursue those future directions instead. However that would only partially address the motivations for this proposal, still leaving a broader pack repetition pattern design needed for abstract tuples.

Future directions

  • Repetition patterns for concrete tuples
  • Pack repetition patterns for arrays containing a value pack where all pack elements are of the same type
  • Lifting the restriction that a tuple containing a value pack must contain only the single value pack and use no label. This would be required to enable pack repetition patterns for a contained value pack amongst arbitrary other tuple elements, addressable via a label.
17 Likes

This looks useful, but I have some questions:

  • Is the last print statement the only thing that ensures that value and tuple have the same number of entries? If so, isn't it a problem that this is not exposed in the function signature?

  • What does it mean that the tuple is "evaluated"? What would happen if that tempTuple step was not there?

  • It seems that an important purpose of this is to enable local pack variables, could you provide an example of how this would work?

1 Like

No, both value and tuple are using the same pack U.

It would be a problem if there were two different packs:

func example<each T, each U>(packElements value: repeat each T, tuple: (repeat each U)) {
    print((repeat each value))
    print((repeat each tuple))

    print((repeat (each value, tuple)))

    // error: pack expansion requires that 'U' and 'T' have the same shape
    print((repeat (each value, each tuple)))
}
6 Likes

Ah of course, thanks!

Neat! So:
(repeat (each value, each tuple)) is somewhat close to what zip is to arrays?

2 Likes

It seems very natural based on SE-0393!

Does this pitch contain map like behavior such as repeat (each value) * 2?

Did someone say fold expressions?

1 Like

The one currently implemented, i.e. the .element value pack, feels more clear and natural at the use site (each pair vs each pair.element).

Even better[1] would be having (repeat each T) to be able to refer to any tuple (both unlabelled and labelled), a slightly different syntax to get each value in the tuple

let tuple = (1, "one")
let result = (repeat type(of: each in tuple))
// result is (Int.self, String.self)

and eventually an alternative one to map labeled tuples preserving their labels

let tuple = (value: 1, name: "one")
let result = (repeat type(of: each of tuple))
// result is (value: Int.self, name: String.self)

The straw-man syntax above uses each in to get rid of labels, and each of to preserve them.


  1. proof needed. ↩ī¸Ž

Is the last print statement the only thing that ensures that value and tuple have the same number of entries? If so, isn't it a problem that this is not exposed in the function signature?

To add the answer @Zollerboy1 gave, I would suggest referring back to the review discussion on SE-0393 for more insight into where shape requirements stand and what future directions are.

What does it mean that the tuple is "evaluated"? What would happen if that tempTuple step was not there?

You could say that this proposal is offering two ways to think about what it means to use an abstract tuple value in a pack repetition pattern: one way to think about it is to treat it like an implicit conversion to a value pack, and the other way is described in the part you are referencing which is to consider an automatic step of "unwrapping" the value pack from the tuple. I think that the pseudo code could be made more clear in that example though.

It seems that an important purpose of this is to enable local pack variables, could you provide an example of how this would work?

No, let me clarify what I wrote in this proposal about that: the preceding SE-0393 proposal itself is what specifies that, in order to enable local value pack variables, the value pack must be "wrapped" in a tuple. So that is not an idea introduced in this proposal, nor does this proposal offer new means of local variable storage of value packs. However what this proposal does offer is a way to work with the value pack contained within such a tuple (beyond reassignment to another abstract tuple value, which does not achieve anything with the contents of the value pack). Perhaps this warrants more clear language as well.

yes!

1 Like

You have been paying attention. :slight_smile:
There are a few reasons why we decided to drop the .element syntax. We believe doing so introduces no ambiguity since there already is the requirement that we can only repeat each on a tuple with a single element that is a type pack, so it seems unnecessary to explicitly call out that the expansion is occurring on that element. The implemented syntax also prevents potential source breakage in the case where a tuple uses a label "element", and prevents a case of designating a reserved tuple label that functions differently than any other tuple label. This also provides more freedom in the "future direction" of allowing labels and more heterogenous elements in tuples containing value pack(s).

I think what you are suggesting, minus any concrete syntax proposals, is covered in the first and last items in the "future directions" section? Or do you feel something is lacking there?

Yes the intention is for such a construct to work (though admittedly I tried it with a type pack each T: Numeric and hit a compiler crash, so there is further bug to be fixed).

3 Likes

Consider the following code under this proposal:

func tuplify<each T>(_ t: repeat each T) -> (repeat each T) {
  print("making tuple")
  return (repeat each t)
}

func expandTuple<each U>(_ u: repeat each U) {
  repeat print(each tuplify(repeat each u))
}

expandTuple(1, "hello", true)

The proposal states that the expanded tuple is evaluated only once, so the pack expansion expression is conceptually evaluated with the tuplify() call outside of the pack loop:

let tempTuple = tuplify(repeat each u) 
repeat print(each tempTuple)

so the output is

making tuple 
1 
"hello" 
true

If tuplify() were evaluated inside the pack loop, the output would be

making tuple 
1 
making tuple 
"hello" 
making tuple 
true
2 Likes