Swift Digest
SE-0399 | Swift Evolution

Tuple of value pack expansion

Proposal
SE-0399
Authors
Sophia Poirier, Holly Borla
Review Manager
Xiaodi Wu
Status
Implemented (Swift 5.9)

01 何が問題だったのか

SE-0393SE-0398 によって、関数と型のジェネリックパラメータは可変個の型パラメータ(parameter pack)を扱えるようになりました。しかし、これだけでは 「value pack を含むタプル値」からその要素にアクセスする手段 がまだ欠けていました。

value pack(値パック)は関数の引数リストやタプル要素、ジェネリック引数リストなど、「値や型の並びを受け取る位置」でしか展開できません。そのため、value pack を関数の戻り値型・型エイリアス・ローカル変数型として扱いたい場合、いったんタプルでくるむ((repeat each T) のような1要素タプル型にする)必要があります。

ところがそうしてタプルに包んでしまうと、今度はその タプルの中身を再び value pack として取り出す方法がない という壁にぶつかります。たとえば、別の variadic な関数から返ってきたタプルを、そのまま別の関数にvalue packとして転送することも、repeat パターンで各要素に対する処理を展開することもできませんでした。

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)
  // abstractTuple はタプルだが、その中の value pack に触る手段がない
}

SE-0393 / SE-0398 で value pack をタプルに格納すること自体は推奨された回避策になっていたにもかかわらず、そのタプルと生の value pack との間で機能面の差が残っており、variadic ジェネリクスの使い勝手を損ねていました。

02 どのように解決されるのか

repeat パターンの中での each を、value pack だけでなく abstract tuple value(抽象タプル値) にも適用できるようにします。abstract tuple value とは、ひとつの型パラメータパックだけを要素に持ち、ラベルも追加要素も持たないタプル値のことです。このようなタプルに each を使うと、外側のタプル構造が取り除かれ、中身の value pack として展開されます。

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

repeat each <タプル式> は、まず <タプル式>一度だけ 評価してから、その結果のタプルに含まれる各要素に対して繰り返しパターンを展開します。意味的には次のように、一時変数に束縛してから展開するのと同じです。

repeat each <tuple expression>

// は次と同じ意味になります
let tempTuple = <tuple expression>
repeat each tempTuple

これにより、variadic な関数から返ってきたタプルをそのまま別の repeat パターンに流し込めるようになります。

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) // error: 具体的な型のタプルは対象外

  let mixedConcreteAndAbstractTuple = (1, repeat each value)
  repeat print(each mixedConcreteAndAbstractTuple) // error: value pack以外の要素がある

  let labeledAbstractTuple = (label: repeat each value)
  repeat print(each labeledAbstractTuple) // error: ラベルがある
}

value pack と abstract tuple value を混ぜて展開する

value pack とタプルの中の value pack は、同じ repeat パターンの中に並べて使えます。each を付けた側だけが要素ごとに展開され、付けない側はパターン全体を通してそのままの値として参照されます。

func example<each T>(packElements value: repeat each T, tuple: (repeat each T)) {
  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))

// 出力:
// (1, 2, 3)
// (4, 5, 6)
// ((1, (4, 5, 6)), (2, (4, 5, 6)), (3, (4, 5, 6)))
// ((1, 4), (2, 5), (3, 6))

3つ目の repeat (each value, tuple) では tupleeach を付けていないため、各繰り返しでタプル全体がそのまま現れます。4つ目の repeat (each value, each tuple) では両方に each が付くので、同じ長さの value pack 同士がzipされるように展開されます(同じ長さであることは型パラメータパック T を共有していることから保証されます)。

対象外のケース

読者のコードがエラーになるのを避けるため、この提案で許されるのは「abstract tuple value」に限られる点を明示的に押さえておきます。具体的には、次のようなタプルは repeat each の対象になりません。

  • 具体的な型のタプル(例: (true, "two", 3)
  • value pack 以外の要素が混ざったタプル(例: (1, repeat each value)
  • ラベル付きのタプル(例: (label: repeat each value)

これらはいずれも「タプル全体を1つの value pack として扱う」という今回の変換に当てはまらないため、コンパイルエラーになります。

Future Directions(speculative)

以下は将来的な拡張として提案内で言及されている方向性で、実現を約束するものではありません。

  • 具体的な型のタプルに対する repeat each の許可((true, "two", 3) のようなタプルを展開できるようにする)
  • 全要素が同じ型・同じ適合を持つ場合に、value pack から Array を作れるようにする
  • value pack 以外の要素やラベルを含むタプルでも、パックとして扱える部分を展開できるように制限を緩める