Skip to content
Open
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
295 changes: 295 additions & 0 deletions TSPL.docc/LanguageGuide/Generics.md
Original file line number Diff line number Diff line change
Expand Up @@ -1853,6 +1853,301 @@ 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.

Parameter packs make it possible to abstract such function over the
number of generic arguments, eliminating the need to type-erase or
provide multiple overloaded copies of the same function with
a variable number of generic arguments.

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) * 2)
}

let doubledNumbers = double(12, 0.5, 8 as Int8)
print(doubledNumbers)
// Prints (24, 1.0, 16)
```

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

There are two types of parameter packs: type packs and value packs. The type
packs are made of type parameters, while value packs are made of values.

At the call site of `double`, the type of the pack `T` will be substituted
for the type pack `{Int, Double, Int8}`. The type of the type pack is derived
from the value pack `12, 0.5, 8 as Int8` passed at the call site. This
guarantees that the corresponding types and values appear at the same
positions in their respective packs.

The `double` function doubles each element of the parameter pack by applying
the `repeat` statement to each element of the value pack multiplied by 2.
The `repeat` statement applied to a value pack denotes a "repetition pattern",
which is a pattern that will be repeated for every value in the pack.

Repetition is a great way to apply a simple transformation on each element
of the parameter pack. It effectively performs iteration on the value pack,
but in a clean and concise way. However, there are cases where your code either
demands a piece of other state to be updated on each iteration, or you want the
iteration to stop when a certain condition is met.

Similarly to collections, value packs support iteration with a `for`-`in repeat`
loop.

```swift
func allEmpty<each T>(_ array: repeat [each T]) -> Bool {
for a in repeat each array {
guard a.isEmpty else { return false }
}

return true
}
```

In the code above, the `allEmpty` function is generic over a type parameter
pack `each T`, and takes a value parameter pack `array`, the type of which is
declared using `repeat [each T]` pack expansion, where `[each T]` is the
repetition pattern.

The `for`-`in repeat` loop gives an opportunity to access elements of the value
pack on each iteration using a local variable. In this case, the local variable
`a` represents the value stored in the value pack on a given iteration. Using
`for`-`in repeat` loop gives you an opportunity to stop the iteration early—in
this case, the iteration will stop once a non-empty array is found.

Here's how you might use the `allEmpty` function:

```swift
print(allEmpty(["One", "Two"], [1], [true, false], []))
// False
```

Using the `for`-`in repeat` loop is useful in cases where you want more control
over the pack iteration—stop the evaluation early for performance, or mutate
some other state on each iteration.

Note that both approaches for iterating over the pack are fine! Choose one that
fits your use-case the most.

> Note: Use singular naming convention when naming your parameter packs.

Parameter packs can contain zero or more arguments. If you need to
require one or more, use a regular generic argument together with the type
pack argument:

```swift
func requireOneOrMore<T, each U>(element: (T, repeat each U)) { }
```

The function above can now be called with at least one value passed into the
`element` parameter, required by the `T` generic argument.

Repetition patterns can be applied to so many types! In the the `nonEmpty`
example above, the `array` parameter's type is declared with `repeat [each T]`,
expanding into multiple array types.

A more complex type can include `each` multiple times. For example:

```swift
func evaluateAll<each V, each E: Error>(
result: repeat Result<each V, each E>
) -> [any Codable] {
// ...
}
```

The above `evaluateAll` function is generic over two different type packs,
`V` and `E`. The type of `result` is declared using the `Result` type that
takes 2 generic parameters. `repeat Result<each V, each E>` expands the
type of the `result` parameter to be a type pack where the expansion of
`each V` and `each E` is required to be of the same size, meaning the
number of types both packs expand to are guaranteed to be equal.

You can also vary the return types of your function based on the parameter
types:

```swift
protocol RequestProtocol {
associatedtype Input
associatedtype Output
func evaluate(_ input: Input) -> Output
}

struct Evaluator<each Request: RequestProtocol> {
var item: (repeat each Request)

func query(_ input: repeat (each Request).Input) -> (repeat (each Request).Output) {
return (repeat (each item).evaluate(each input))
}
}
```

In the above code example, the protocol `RequestProtocol` requires a function
`evaluate` that, given an input of an associated type `Input`, returns the
other associated type `Output`.

The `Evaluator` struct is generic over the `Request` type, which is a parameter
pack. Like with regular generics, parameter packs support type constraints—each
element in the `Request` type pack has to conform to the `RequestProtocol`
protocol.

The `item` property stores the elements of the `Request` parameter
pack in a tuple.

The `query` methods operates on an `input` argument of
`repeat (each Request).Input` type. Because each element of the `Request`
type pack conforms to `RequestProtocol`, the implementation of `query` is able
to call the protocol requirement `evaluate`, using the elements of the `input`
parameter pack as its input. As a result, a new pack containing
`RequestProtocol.Output` type is produced.

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`. In this example, the `repeat`
keyword applied in front of `(each item).evaluate(each input)` call
calls `evaluate` function on each pair of elements from `item` and `input`
value packs, respectively. Again, such construction guarantees that `item`
and `input` packs have to be of the same length. As a result, the tuple
returned by `query` also has the same length.

You can also use trailing `where` clause with parameter packs in more complex
cases. For example, consider the following type:

```swift
struct Container<each Element> {
var value: (repeat each Element)
}
```

With the `where` clause, it is easy to conditionally constrain the type to
conform to protocols using the types in the `Element` parameter pack:

```swift
extension Container: Sendable where repeat each Element: Sendable {}
```

In the code above, `Container` conditionally conforms to `Sendable` when each
element of the type parameter pack `Element` conforms to `Sendable`, making it
usable in contexts requiring sendability.

The types that appear in a type parameter pack must be constrained if its
expansion is used as a constrained generic type parameter. For example,
types such as `Set` require its generic argument to conform to `Hashable`,
so each element of the parameter pack needs to be constrained:

```swift
struct Container<each Element: Hashable> {
var value: (repeat each Set<Element>)
}
```

Using all of the concepts above, parameter packs provide powerful capabilities
in your code. Let's consider a more advanced example of using parameter packs,
and start off by declaring the following protocol:

```swift
protocol ValueProducer {
associatedtype Value: Codable
func evaluate() -> Value
}
```

The above protocol `ValueProducer` requires the `evaluate()` method that’s
return type is the associated type `Value` that conforms to `Codable` protocol.

For example, suppose you get a parameter pack of values of type
`Result<ValueProducer, Error>`, and you need to iterate only over the success
elements and call the `evaluate()` method on its value. Also, suppose you need
to save the result of each call into an array.

```swift
func evaluateAll<each V: ValueProducer, each E: Error>(result: repeat Result<each V, each E>) -> [any Codable] {
var evaluated: [any Codable] = []
for case .success(let valueProducer) in repeat each result {
evaluated.append(valueProducer.evaluate())
}

return evaluated
}
```

In the body of `evaluateAll`, you can use the `for`-`in repeat` loop
for performing an operation while
iterating over the values stored in the `result` value pack. Note that you
can use a familiar `for case` pattern to only iterate over the `.success`
case of the `Result` enum. You can bind the value using the `valueProducer`
variable. Finally, on each iteration, you can append the result of the call
to the `evaluate()` method to the `evaluated` array, which gets returned
from the function.

## 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
Loading