Skip to content

Commit b544739

Browse files
OguzYuukselOguz Yuksel
authored and
Oguz Yuksel
committed
Propose Pack Destructuring & Pack Splitting
1 parent 1d30063 commit b544739

File tree

1 file changed

+183
-0
lines changed

1 file changed

+183
-0
lines changed
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
# SE-NNNN: Pack Destructuring & Pack Splitting
2+
3+
* Proposal: SE-0XXX
4+
* Authors: Oguz Yuksel
5+
* Review Manager: TBD
6+
* Status: Draft
7+
8+
## Introduction
9+
Swift’s variadic generics story (SE‑0393 “Parameter Packs” and SE‑0408 “Pack Iteration”) unlocks powerful abstractions over arbitrary arities. However, two missing features—recursive decomposition of packs in patterns and split‑expansion of packs at call sites—force library authors into brittle workarounds like fixed‑arity overloads or type erasure. This proposal completes the variadic‑generic toolbox by introducing:
10+
11+
1. **Pack Destructuring in Patterns** – extract `head` and `tail` from a variadic tuple in a `let` or `switch` pattern.
12+
2. **Pack Splitting in Calls** – feed a single parameter‐pack expansion into separate head + tail parameters at call sites.
13+
14+
These additions eliminate boilerplate, remove arbitrary arity limits, and enable truly recursive generic algorithms.
15+
16+
## Motivation
17+
- **No recursive pack decomposition**: We cannot peel off the first element of a parameter pack at compile time.
18+
- **Fixed‑arity overloads & limits**: Combine’s `zip` (up to 10 overloads) and SwiftUI’s `TupleView` (10 views max) are symptomatic workarounds.
19+
- **Type erasure** sacrifices compile‑time safety and incurs runtime costs (`[AnyPublisher]`).
20+
- **Goal**: Give library authors a first‐class, type‑safe, zero‑overhead mechanism to recurse over arbitrary variadic tuples.
21+
22+
## Proposed Solution
23+
I propose two orthogonal features:
24+
25+
1. **Pack Destructuring in Patterns**
26+
2. **Pack Splitting in Calls**
27+
28+
Both are purely compile‑time, require no ABI changes, and integrate smoothly with existing tuple layout and call‐site resolution rules.
29+
30+
## Detailed Design
31+
32+
### Pack Destructuring in Patterns
33+
**Syntax**
34+
```swift
35+
let (head, repeat each tail) = variadicTuple
36+
```
37+
38+
**Semantics**
39+
- `head` binds to the first element of the tuple.
40+
- `tail` binds to a new pack containing the remaining elements.
41+
- Exactly one `repeat each` may appear per tuple‐level.
42+
43+
**Examples**
44+
```swift
45+
// New: peel off first element
46+
let tuple: (Int, String, Bool) = (1, "a", true)
47+
let (first, repeat each rest) = tuple
48+
// first: Int = 1
49+
// rest: (String, Bool)
50+
51+
switch result {
52+
case .success(let firstValue, repeat each otherValues):
53+
process(firstValue, repeat each otherValues)
54+
case .failure(let error):
55+
handleError(error)
56+
}
57+
```
58+
59+
**Static Checks**
60+
- If the pack is empty, pattern matching fails (compile‐time error).
61+
- Only one `repeat each` per tuple—extra expansions or mismatched arity are diagnostics.
62+
63+
### Pack Splitting in Calls
64+
**Syntax**
65+
```swift
66+
func process(_ head: Head, _ tail: repeat each Tail) { }
67+
68+
// Given a pack:
69+
let pack: (A, B, C, D) = ()
70+
71+
// Call with split:
72+
process(repeat each pack)
73+
// Desugars to:
74+
// process(pack.0, (pack.1, pack.2, pack.3))
75+
```
76+
77+
**Semantics**
78+
1. Non‑pack parameters bind first (in declaration order).
79+
2. A single `repeat each` parameter consumes all remaining elements as a pack.
80+
81+
**Example**
82+
```swift
83+
func flatten(_ values: repeat each T) -> (repeat each T) {
84+
guard let (first, repeat each rest) = (repeat each values) else {
85+
return () // empty pack
86+
}
87+
return (first, repeat each flatten(rest))
88+
}
89+
```
90+
91+
## Technical Design
92+
93+
### Grammar Extension
94+
```ebnf
95+
tuple-pattern-element → pattern
96+
| 'repeat' 'each' identifier
97+
tuple-expression-element → expression
98+
| 'repeat' 'each' pack-expression
99+
```
100+
101+
### AST & Type System
102+
- Introduce `PackExpansionPattern` and `PackExpansionExpr` nodes.
103+
- Enforce at most one `repeat each` at each tuple nesting level.
104+
- Match arity at compile time; no runtime checks needed.
105+
106+
### Source Compatibility
107+
This feature is purely additive. Existing code—packs, expansions, and overload resolution—continues to compile without change.
108+
109+
### ABI Stability
110+
No new runtime structures or calling conventions are introduced. All destructuring is compiled to existing tuple projection instructions.
111+
112+
### Performance
113+
Compile‑time type checking adds negligible cost. Generated code for tuple access and calls is identical to hand‑written projections.
114+
115+
## Argument Binding Rules
116+
1. Bind all non‑pack parameters in declaration order.
117+
2. The single `repeat each` parameter then consumes the remaining arguments as one pack.
118+
3. Extra or missing arguments produce diagnostics as usual.
119+
120+
```swift
121+
func accept(a: Int, b: String, c: repeat each C) { }
122+
123+
// Valid:
124+
accept(repeat each (1, "a", true, false))
125+
// a = 1, b = "a", c = (true, false)
126+
127+
// Error: too few args
128+
accept(repeat each (1, "a"))
129+
```
130+
131+
## Impact on Standard Library & Real‑World Examples
132+
133+
### Combine.zip Before
134+
```swift
135+
// 10+ overloads:
136+
func zip<A,B>(_ a: A, _ b: B) -> Zip2<A,B>
137+
func zip<A,B,C>(_ a: A, _ b: B, _ c: C) -> Zip3<A,B,C>
138+
// … up to Zip10
139+
```
140+
141+
### Combine.zip After
142+
```swift
143+
struct Zip<repeat each S>: Publisher {
144+
typealias Output = (repeat (each S).Output)
145+
146+
let publishers: (repeat each S)
147+
148+
func receive<Sub: Subscriber>(subscriber: Sub) where Sub.Input == Output {
149+
let (first, repeat each rest) = publishers
150+
first.receive(ZipHelper(subscriber, rest))
151+
}
152+
}
153+
```
154+
155+
**Result**: ~250 LOC → ~20 LOC, no fixed arity limit, compile‑time safety preserved.
156+
157+
## Future Directions
158+
- **Multi‑Pack Operations**
159+
```swift
160+
func zip(_ s1: repeat each S1, _ s2: repeat each S2) -> (repeat each (S1, S2))
161+
```
162+
- **Named Pack Elements**
163+
```swift
164+
let (header: repeat each headers, body: repeat each bodies) = httpResponse
165+
```
166+
- **Variadic async/await**
167+
```swift
168+
async let (firstResult, repeat each partials) = fetchAll(repeat each requests)
169+
```
170+
171+
## Alternatives Considered
172+
173+
### 1. Fixed‑Arity Overloads
174+
- Pros: trivial, no language changes
175+
- Cons: boilerplate, arbitrary limits, code bloat
176+
177+
178+
## References
179+
1. [SE‑0393: Parameter Packs](https://github.com/apple/swift-evolution/blob/main/proposals/0393-parameter-packs.md) – Introduces variadic type‐parameter packs.
180+
2. [SE‑0408: Pack Iteration](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0408-pack-iteration.md) – Enables iteration over packs.
181+
182+
## Conclusion
183+
By adding pack destructuring in patterns and pack splitting in calls, this proposal fills the last gaps in Swift’s variadic‑generic capabilities. Library authors can now write truly recursive, zero‑overhead abstractions without fixed arity limits or type erasure, while preserving source and ABI compatibility.

0 commit comments

Comments
 (0)