Swift Digest
SE-0439 | Swift Evolution

Allow trailing comma in comma-separated lists

Proposal
SE-0439
Authors
Mateus Rodrigues
Review Manager
Xiaodi Wu
Status
Implemented (Swift 6.1)

01 何が問題だったのか

Swift では以前から、配列と辞書のリテラルに限って末尾のカンマ(trailing comma)を書けました。

let rank = [
  "Player 1",
  "Player 3",
  "Player 2",
]

末尾のカンマが許されていると、最後の要素を追加・削除・並べ替えたり、コメントアウトしたりするときに、他の要素と同じように扱えます。diff も最終行の差分だけで済み、レビューがしやすくなります。

一方で、配列や辞書以外のカンマ区切りリスト、たとえば関数呼び出しの引数リストなどでは末尾のカンマが許されておらず、同じような取り回しができませんでした。デフォルト引数を持つ関数で一部の引数だけコメントアウトしたい場合など、現実的によく起きる場面でエラーになります。

let numbers = [1, 2, 0, 3, 4, 0, 0, 5]

let subsequences = numbers.split(
    separator: 0,
//    maxSplits: 1
) // error: Unexpected ',' separator

関連する提案は Swift 3 の頃(SE-0084)にも一度検討されましたが、当時は「閉じ括弧を引数の次の行に置くコードスタイル」がまだ定着していないという理由で見送られました。その後、このスタイルはコミュニティ・標準ライブラリ・swift-format・DocC・Xcode などで広く使われるようになり、見送りの前提が崩れていました。

加えて、parameter pack(SE-0393)の導入で可変個の型パラメータを扱う API が書けるようになったり、マクロやプラグインによるコード生成でカンマ区切りリストを生成する機会が増えたりしたことで、「最後の要素だけ特別扱いしないで済む」ことの価値も以前より大きくなっています。

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

対称的な区切り文字((...)[...]<...>)で囲まれたカンマ区切りリストに対して、末尾のカンマを書けるようにします。閉じ括弧が必ずあるため、末尾のカンマがあっても曖昧なく解釈できる、という考え方です。

末尾のカンマが書けるようになる場所

タプルとタプルパターンで書けます。

let velocity = (
    1.66007664274403694e-03,
    7.69901118419740425e-03,
    6.90460016972063023e-05,
)

let (
    velocityX,
    velocityY,
    velocityZ,
) = velocity

イニシャライザ・関数・enum の associated value・式マクロ・属性の、パラメータリストおよび引数リストで書けます。

func foo(
    input1: Int = 0,
    input2: Int = 0,
) { }

foo(
    input1: 1,
    input2: 1,
)

enum E {
    case foo(
        input1: Int = 0,
        input2: Int = 0,
    )
}

@Foo(
    "input 1",
    "input 2",
    "input 3",
)
struct S { }

#foo(
    "input 1",
    "input 2",
    "input 3",
)

subscript(key path subscript も含む)でも書けます。

let value = m[
    x,
    y,
]

let keyPath = \Foo.bar[
    x,
    y,
]

クロージャのキャプチャリストでも書けます。

{ [
    capturedValue1,
    capturedValue2,
] in
}

ジェネリックパラメータリストとジェネリック引数リストでも書けます。

struct S<
    T1,
    T2,
    T3,
> { }

let s = S<
    T1,
    T2,
    T3,
>()

文字列補間の引数リストでも書けます。

let s = "\(1, 2,)"

末尾のカンマが書けない場所

対称的な区切り文字を持たないカンマ区切りリストでは、末尾のカンマをどこで終わりと判断するかが曖昧になるため、今回の提案では対象外です。具体的には次のような場所では従来どおり末尾のカンマを書けません。

  • if / guard / while の条件リスト
  • enum の case ラベルの連続した列挙
  • switchcase ラベル
  • 継承節(struct S: P1, P2, P3
  • ジェネリックの where
if
    condition1,
    condition2, // error
{ }

struct S:
    P1,
    P2,
    P3, // error
{ }

たとえば if の条件リストを { で区切ろうとしても、条件の中に { true }() のようなクロージャを含められるため、終端を一意に決められません。継承節や where 節も、protocol 定義の中では次の宣言との境界が明確ではなく、同様に末尾のカンマを曖昧なく受け入れにくい構造です。

要素数と末尾のカンマ

対象のリストが1要素だけの場合でも末尾のカンマを書けます。一方で、0要素のリストに対しては書けません。末尾のカンマはあくまで「最後の要素に付いているカンマ」であり、要素ゼロで書けるようにすると別途「先頭のカンマ」を許すことになってしまうためです。

(1,) // OK
(,) // error: expected value in tuple

また、型リストのようにそもそもカンマ区切りリストとして扱われない場所や、引数をカンマ区切りリストとしてパースしない組み込み属性では、引き続き末尾のカンマは書けません。

let x: [Int,] // error
@inline(never,) // error