diff --git a/TSPL.docc/GuidedTour/GuidedTour.md b/TSPL.docc/GuidedTour/GuidedTour.md index 7f873312b..84ea87d2f 100644 --- a/TSPL.docc/GuidedTour/GuidedTour.md +++ b/TSPL.docc/GuidedTour/GuidedTour.md @@ -2380,6 +2380,17 @@ anyCommonElements([1, 2, 3], [3]) Writing `` is the same as writing ` ... where T: Equatable`. +Use `repeat` and `each` to make generic functions +where the number of arguments can vary. + +``` +func printEach(_ 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. diff --git a/TSPL.docc/LanguageGuide/Generics.md b/TSPL.docc/LanguageGuide/Generics.md index 5e5e9d417..14bffb21b 100644 --- a/TSPL.docc/LanguageGuide/Generics.md +++ b/TSPL.docc/LanguageGuide/Generics.md @@ -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(_ value: T) -> T { + return 2 * value +} + +func double(_ value1: T, _ value2: U) -> (T, U) { + return (2 * value1, 2 * value2) +} + +func double( + _ 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. + + + +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(_ 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(_ 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(_ 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` + where the `Payload` type pack contains `Bool`, `Int`, and `String` + produces `Request, Request, Request`. + +- 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` expands to multiple array types. + +- A more complex type can include `each` multiple times, + like `repeat Dictionary`. + 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` + 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` 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. + +- 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 + 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: + + + ```swift + func tuplify(_ value: repeat each T) -> (repeat each T) { + return (repeat each value) + } + + func example(_ 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, diff --git a/TSPL.docc/ReferenceManual/Expressions.md b/TSPL.docc/ReferenceManual/Expressions.md index c72d9a67e..3502f4a36 100644 --- a/TSPL.docc/ReferenceManual/Expressions.md +++ b/TSPL.docc/ReferenceManual/Expressions.md @@ -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) + + + +> Grammar of a pack-expansion expression: +> +> *parameter-pack-expression* → **`each`** *expression* +> +> *parameter-pack-expansion-expression* → **`repeat`** *expression* + ### Wildcard Expression A *wildcard expression* diff --git a/TSPL.docc/ReferenceManual/Statements.md b/TSPL.docc/ReferenceManual/Statements.md index 8f9913cf7..4f7e9597d 100644 --- a/TSPL.docc/ReferenceManual/Statements.md +++ b/TSPL.docc/ReferenceManual/Statements.md @@ -70,17 +70,19 @@ and a `continue` statement and is discussed in 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. @@ -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* diff --git a/TSPL.docc/ReferenceManual/Types.md b/TSPL.docc/ReferenceManual/Types.md index 710f61944..2d77cca71 100644 --- a/TSPL.docc/ReferenceManual/Types.md +++ b/TSPL.docc/ReferenceManual/Types.md @@ -46,6 +46,7 @@ and describes the type inference behavior of Swift. > *type* → *protocol-composition-type* \ > *type* → *opaque-type* \ > *type* → *boxed-protocol-type* \ +> *type* → *parameter-pack-type* > *type* → *metatype-type* \ > *type* → *any-type* \ > *type* → *self-type* \ @@ -948,6 +949,51 @@ https://github.com/apple/swift-evolution/blob/main/proposals/0335-existential-an > > *boxed-protocol-type* → **`any`** *type* +## Type Parameter Pack + +XXX OUTLINE: + +- `each T` creates a type parameter pack + when it appears in a generic parameter clause, + or indicates which pack should be expanded + when it appears in a `repeat` expression + +- `repeat P`, where `P` captures at least one type + parameter pack, expands it into a list of types + or values + +- Packs are never nested; expansion implies flattening + +- To expand the values, you use a parameter pack expression; + see + +- It's valid for a type pack to contain no elements. + +- list of places where type parameter pack can appear: + + + generic type parameter list + + as an argument to a generic type parameter + + tuple element + + function type parameter list + +> Grammar of a type-parameter pack: +> +> *type-parameter-pack* → **`each`** *type* +> +> *type-parameter-pack-expansion* → **`repeat`** *pattern-type* +> +> *pattern-type* -> *type* + + + ## Metatype Type A *metatype type* refers to the type of any type,