Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add documentation for parameter packs #174

Draft
wants to merge 16 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions TSPL.docc/GuidedTour/GuidedTour.md
Original file line number Diff line number Diff line change
Expand Up @@ -2380,6 +2380,17 @@ anyCommonElements([1, 2, 3], [3])
Writing `<T: Equatable>`
is the same as writing `<T> ... where T: Equatable`.

Use `repeat` and `each` to make generic functions
where the number of arguments can vary.

```
func printEach<each T>(_ t: repeat each T) {
repeat print(each t)
}

printEach(1, "hello", true)
```

> Beta Software:
>
> This documentation contains preliminary information about an API or technology in development. This information is subject to change, and software implemented according to this documentation should be tested with final operating system software.
Expand Down
279 changes: 279 additions & 0 deletions TSPL.docc/LanguageGuide/Generics.md
Original file line number Diff line number Diff line change
Expand Up @@ -1853,6 +1853,285 @@ protocol ComparableContainer: Container where Item: Comparable { }
}
-->

## Generic Parameter Packs

To understand the problem that parameter packs solve,
consider the following overloads:

```swift
func double<T: Numeric>(_ value: T) -> T {
return 2 * value
}

func double<T: Numeric, U: Numeric>(_ value1: T, _ value2: U) -> (T, U) {
return (2 * value1, 2 * value2)
}

func double<T: Numeric, U: Numeric, V: Numeric>(
_ value1: T,
_ value2: U,
_ value3: V) -> (T, U, V) {
return (2 * value1, 2 * value2, 2 * value3)
}
```

Each function in the example above
takes a different number of arguments,
and returns a tuple containing the result of doubling those arguments.
Because the functions are generic,
they can take values of any numeric type,
and each argument can be a different type ---
and the doubled values that are returned have those same types.
Manually writing out each function like this
is repetitive and can be error prone.
It also imposes an arbitrary limit of three arguments,
even though the doubling operation could really apply
to any number of arguments.

Other approaches that also have drawbacks
include taking the arguments as an array or a variadic parameter,
which requires all arguments to be the same type,
or using `Any` which erases type information.

<!-- XXX transition -->

Writing this function with a parameter pack
preserves type information about its arguments,
and lets you call the function with an arbitrary number of arguments:

```swift
func double<each T: Numeric>(_ value: repeat each T) -> (repeat each T) {
// ...
}
```

In the code above, `each T` is declared in a generic parameter list.
It's marked with `each`, indicating that it's a type parameter pack.
In contrast to a generic type parameter,
which serves as a placeholder for a single type,
a type parameter pack is a placeholder for multiple types.
The ability to have `T` contain a varying number of types
is what allows this version of `double(_:)`
to take any number of parameters
while preserving the type information about each of its parameters.

There isn't any syntax in Swift to write out the list of types,
but you use `repeat` and `each`
to mark code that is repeated for each value in that list.

```swift
func double<each T: Numeric>(_ value: repeat each T) -> (repeat each T) {
return (repeat (each value).doubled())
}


extension Numeric {
func doubled() -> Self {
return 2 * self
}
}

let numbers = [12, 0.5, 8 as Int8]
let doubledNumbers = doubled(numbers)
```

The value of `doubledNumbers` is `(24, 1.0, 16)`,
and each element in this tuple has the same type
as the value in `numbers` that it comes from.
Both `12` and `24` are `Int`,
`0.5` and `1.0` are `Double`,
and `8` and `16` are `Int8`.

* * *

XXX OUTLINE:

How do you read a parameter pack at its call site?

- A parameter pack "packs" together types.
A pack that's made up of types is called a *type pack*.
A pack that's made up of values is called a *value pack*.

- A type pack provides the types for a value pack.
The corresponding types and values appear at the same positions
in their respective packs.

- When you write code that works on collections, you use iteration.
Working with parameter packs is similar ---
except each element has a different type,
and instead of iteration you use repetition.

How do you create a parameter pack?

- In the generic type parameters,
write `each` in front of a generic argument
to indicate that this argument creates a type parameter pack.

- In the function's parameters,
write `repeat` in front of the type for the parameter
that can accept a variable number of arguments,
to create an *expansion pattern*.
You can also write `repeat` in the function's return type.

- The expansion of a repetition pattern produces a comma-separated list.
It can appear in generic argument lists,
in tuples types,
and in function argument lists.

```swift
func f<each T>(_ t: repeat each T) -> (repeat each T)
```

- The expansion pattern is repeated for every element in the given type pack
by iterating over the types in the type pack
and replacing the type placeholder that comes after `each`.

For example, expanding `repeat Request<each Payload>`
where the `Payload` type pack contains `Bool`, `Int`, and `String`
produces `Request<Bool>, Request<Int>, Request<String>`.

- When the type of a function argument is a type pack,
the values that are passed in for that argument become a value pack.

- Naming convention:
Use singular names for parameter packs,
and plural names only in argument labels.

Note:
Parameter packs can contain zero or more arguments.
If you need to require one or more,
use a regular parameter before the pack parameters.

What else can you repeat?
How do you repeat more than one type?

- In the simple case, where only one type repeats,
you write `repeat each T` or similar.

- For collections or other generic types,
the pack expansion can happen inside,
like `repeat Array<each T>` expands to multiple array types.

- A more complex type can include `each` multiple times,
like `repeat Dictionary<each Key, each Value>`.
All of the expansions have to be the same size ---
in this example,
the list of types that `Key` expands to must be the same length
as the list that `Value` expands to.

How do you vary the return types, based on the parameter types?
(TODO: Worked code example.
Use WWDC 2023 session 10168 example at 16 minutes
as a starting point.)

How do you constrain the types in a parameter pack?

- In the simple case,
write the `where` clause in the generic parameters list.
`func foo<each T: Runcible>`
requires all of the types passed in the `T` type pack to conform.

- In the more complex case,
use `repeat each T ` in a trailing `where` clause.

- You must restrict the types that appear in a type parameter pack
if its expansion will be used as a restricted generic type parameter.
For example, given `each T: Hashable` writing `repeat Set<each T>` works,
but it doesn't work with just `each T`
because `Set` requires `T` to be hashable but the pack doesn't.

How do you access the values of a parameter pack?

- Inside the function body, you use `repeat`
to mark places where the code expands the elements of a parameter pack.

- When you use `repeat` at the start of a line (as a statement),
the whole line is duplicated once for each type.
When you use `repeat` in the middle of a line (as an expression),
it expands to make a tuple
with one tuple element for each type.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... only if you have done so within tuple parentheses?


- After `repeat`, you write `each` in front of the type being expanded.
You can expand multiple packs in the same repeat expression,
as long as all of the packs have the same length.

```swift
repeat print(each t)
return (Pair(each first, each second))
```

- The result (and its type) of expanding a parameter pack
simanerush marked this conversation as resolved.
Show resolved Hide resolved
within a tuple vary depending on the number of elements in the pack.
Zero-element packs produce `()`,
single-element packs produce a simple type,
and multi-element packs produce a tuple type.

- You don't always write `repeat each` one after the other.
The part to repeat is marked `repeat`
and the location of the element from the pack is marked `each`.

For example,
`repeat (each T, Int)` is different from `(repeat each T, Int)`.
The former makes multiple tuples `(T1, Int) ... (Tn, Int)`,
expanding `T` and adding `Int` to each tuple.
The latter makes one tuple, `(T1, ..., Tn, Int)`.
Other code can come between `repeat` and `each` ---
both of those are different from `repeat (Int, each T)`

- Tripping hazard:
You can call methods on the repeated values,
as in `repeat (each x).doSomething` ---
and the grouping parentheses are required there ---
but you can't include operators like `repeat (1 + each x)`
as part of the pack expansion.

- You can expand a parameter pack's values
only inside a tuple or a function call.
(TR: Also inside arguments to a macro?
Doug brought that up during the SE review.)
A notable omission is that
there isn't a way to iterate over the values in a pack ---
the SE proposal calls that out as a potential future direction.

- When a function returns a tuple using pack expansion,
or otherwise created by expanding a value pack,
you can perform the same operations on that tuple
as if it were still an unexpanded parameter pack.
This doesn't include tuples created any other way,
or tuples that contain a mix of elements created this way and another way.

For example:

<!-- from SE-0399 -->
```swift
func tuplify<each T>(_ value: repeat each T) -> (repeat each T) {
return (repeat each value)
}

func example<each T>(_ value: repeat each T) {
let abstractTuple = tuplify(repeat each value)
repeat print(each abstractTuple) // Ok

let concreteTuple = (true, "two", 3)
repeat print(each concreteTuple) // Invalid
}
```

How do you work with errors?

- Throwing or propagating an error stops iteration over the value pack.

- For example,
to return a result only if none of the calls threw an error:

```
do {
return (repeat try (each item).doSomething())
} catch {
return nil
}
```

## Generic Subscripts

Subscripts can be generic,
Expand Down
43 changes: 43 additions & 0 deletions TSPL.docc/ReferenceManual/Expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -1477,6 +1477,49 @@ A single expression inside parentheses is a parenthesized expression.
> *tuple-element-list* → *tuple-element* | *tuple-element* **`,`** *tuple-element-list* \
> *tuple-element* → *expression* | *identifier* **`:`** *expression*

### Parameter-Pack Expression

XXX OUTLINE:

- A `repeat` expression must contain one or more `each` expressions
and has the shape `repeat <#repetition pattern#>`

- TODO list of contexts where expansion is supported

+ in a tuple, producing tuple elements
+ as a statement, including at top level, repeating the statement's expression
+ but not in a function call, producing arguments
+ in a `for`-`each` loop

- The *repetition pattern* is repeated once for each type in the pack

- If an expression includes both the `repeat` operator and
a `try` or `await` operator,
the `repeat` operator must appear first.
(So `repeat try each foo` or `repeat each try foo`)

- All of the `each` expressions in a pattern expression
must expand packs that have the same number of types.

- In a function declaration,
an argument whose type is a parameter-pack expansion type
must be the last parameter
or the parameter after it must have a label.

- It's valid for a pack expression to contain no elements,
in which case the parameter-pack expansion expression isn't evaluated at all (zero times)

<!--
You can't write ( immediately after `each`
because `each(` is parsed as a function call.
-->

> Grammar of a pack-expansion expression:
>
> *parameter-pack-expression* → **`each`** *expression*
>
> *parameter-pack-expansion-expression* → **`repeat`** *expression*

### Wildcard Expression

A *wildcard expression*
Expand Down
23 changes: 20 additions & 3 deletions TSPL.docc/ReferenceManual/Statements.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,19 @@ and a `continue` statement and is discussed in <doc:Statements#Break-Statement>
A `for`-`in` statement allows a block of code to be executed
once for each item in a collection (or any type)
that conforms to the
[`Sequence`](https://developer.apple.com/documentation/swift/sequence) protocol.
[`Sequence`](https://developer.apple.com/documentation/swift/sequence) protocol,
or in a value parameter pack.

A `for`-`in` statement has the following form:

```swift
for <#item#> in <#collection#> {
for <#item#> in <#expression#> {
<#statements#>
}
```

The `makeIterator()` method is called on the *collection* expression
If the *expression* of a `for`-`in` statement is a collection *expression*,
the `makeIterator()` method is called on it
to obtain a value of an iterator type --- that is,
a type that conforms to the
[`IteratorProtocol`](https://developer.apple.com/documentation/swift/iteratorprotocol) protocol.
Expand All @@ -93,6 +95,21 @@ and then continues execution at the beginning of the loop.
Otherwise, the program doesn't perform assignment or execute the *statements*,
and it's finished executing the `for`-`in` statement.

The *expression* of a `for`-`in` statement
may also be a pack expansion *expression*.
In this case,
on the *i*th iteration,
the type of *item* is the *i*th type parameter
in the type parameter pack being iterated over.
The value of *item* is the pattern type of *expression*,
with each captured type parameter pack replaced with
an implicit scalar type parameter with matching requirements.
The *expression* is evaluated once at each iteration,
instead of `n` times eagerly,
where `n` is the length of the packs captured by the pattern.
The program will continue executing *statements*
while the total length of the packs captured by the pattern isn't reached.

> Grammar of a for-in statement:
>
> *for-in-statement* → **`for`** **`case`**_?_ *pattern* **`in`** *expression* *where-clause*_?_ *code-block*
Expand Down
Loading