Eliding commas from multiline expression lists
01 何が問題だったのか
Swift では、文(statement)を区切るのにセミコロン ; を使いますが、改行が入っていればセミコロンは省略できます。一方で、式のリスト(関数呼び出しの引数、配列リテラルなど)の区切りであるカンマ , は、改行で区切っていても常に必要でした。
この非対称性のせいで、複数行にわたる式のリストではカンマが「どこからどこまでが1つの式か」という情報を何も伝えていないのに、書き手・読み手・コンパイラすべてに存在を意識させる余計なノイズになる、というのがこの提案の出発点です。
たとえば引数の多いイニシャライザ呼び出しでは、次のように各行末にカンマが並びます。
let protectionSpace = URLProtectionSpace(
host: host,
port: url.port ?? 0,
protocol: url.scheme,
realm: host,
authenticationMethod: NSURLAuthenticationMethodHTTPBasic
)
これらのカンマは人間にとってもコンパイラにとっても改行以上の情報を持っておらず、最終行だけカンマが無いという非対称性も生んでいます。
宣言的 EDSL が不利になる
問題がより顕著に出るのが、Swift に組み込まれた宣言的な EDSL(domain-specific language)です。たとえばテーブル定義の EDSL を書きたいとき、本当は「カラムの宣言の並び」として見せたいのに、実体が関数呼び出しの引数リストであるため、各行末のカンマが常に「これは引数のリストです」と主張してしまいます。
let table = Table(
name: "Employees",
columns:
guid ("record_id", isPrimaryKey: true, nullable: false),
guid ("manager_id", isPrimaryKey: false, nullable: true),
string ("name", length: 1024, nullable: false),
int64 ("employee_id", nullable: false),
date ("start_date", nullable: false)
)
文のリストではセミコロンを省略できるのに、式のリストではカンマを省略できない――この非対称性は、Swift が暗黙のうちに命令的なスタイルを優遇し、宣言的なスタイルにはノイズを強いている、という見方もできます。
編集時の不便さ
さらに、末尾カンマが許されているのはコレクションリテラルだけで、関数呼び出しなどの他の式リストでは許されていません。そのため次のような「最後の要素をコメントアウトする」編集が、関数呼び出しではエラーになります。
print(
"red",
"green",
"blue", // error: unexpected ',' separator
// "cerulean"
)
末尾カンマを全面的に許すという対処もあり得ますが、それはカンマというノイズを増やす方向の解決であって、根本の「複数行の式リストにおけるカンマが無意味」という問題には答えていません。
02 どのように解決されるのか
この提案は Returned for revision(差し戻し) となりました。したがって、ここで説明するルールはあくまで提案されていた内容であり、Swift には取り込まれていません。現在の Swift でも、複数行の式リストではカンマを省略することはできません。
提案されていた内容
複数行にわたる式リストで、式と式の間に改行がある場合に限り、カンマを省略できるようにする、というものでした。対象となる式リストは幅広く、配列リテラル・辞書リテラル・関数呼び出し・メソッド呼び出し・イニシャライザ呼び出し・super 呼び出し・サブスクリプト・タプル・enum の連想値など、ほぼすべての式リスト位置が含まれていました。
文法上は次のような追加で、既存の「カンマ区切り」はそのまま残し、選択肢として「改行区切り」を加える形です。ソースコンパチブルな変更で、既存コードはすべて従来通りに動く想定でした。
expression-list -> expression
| expression , expression-list
| expression \n expression-list
結果として、先ほどの URLProtectionSpace は次のようにカンマ無しで書けるようになります。
let protectionSpace = URLProtectionSpace(
host: host
port: url.port ?? 0
protocol: url.scheme
realm: host
authenticationMethod: NSURLAuthenticationMethodHTTPBasic
)
最後の要素をコメントアウトする編集も、末尾カンマの有無を気にせず行えるようになります。
print(
"red"
"green"
"blue"
// "cerulean"
)
それでもカンマが必要な2つのケース
すべての改行でカンマを落とせるわけではなく、「改行を置いても前の式がまだ続いていると解釈される」2つのケースでは、引き続きカンマを書く必要がある設計でした。セミコロン省略のときと同じ事情で、maximal munch(できる限り長く1つの式として食い続ける)のルール上、改行だけでは式の終わりだと判断できない場合があるためです。
1つ目は、改行の直後にメンバ式(.baz のようなドットで始まる式)が続く場合です。次のコードは foo(bar.baz) と解釈され、foo(bar, .baz) にはなりません。
foo(
bar
.baz
)
implicit member expression として .baz を独立した引数にしたいなら、明示的にカンマが必要です。
foo(
bar,
.baz
)
2つ目は、改行の直後にクロージャ { ... } が続く場合です。これは trailing closure として前の式に付くと解釈され、別の引数にはなりません。独立した引数にしたいときは、やはりカンマを書く必要があります。
foo(
bar,
{ print("baz") }
)
提案では、これら曖昧になりうる位置については、fix-it 付きの診断(必要ならカンマ挿入、またはインデント調整)で書き手を誘導することも併せて想定されていました。
この設計の意図は、「カンマが意味を持つのは、改行だけでは前の式が終わってくれないときに限る」という状態にすることです。省略できるところは省略してしまえば、書かれているカンマは「ここで式を切りたい」という意図を表すシグナルとして読めるようになります。
なぜ Returned for revision になったか
レビューの結果、方向性には支持がありつつも、省略可能とする対象の広さ(タプルや dictionary を含むあらゆる式リスト)、曖昧ケースの扱い、ツーリングへの影響などについて、もう少し絞り込んだ形での再提案が必要と判断されて差し戻されました。以降、これと同じ形で Swift Evolution に戻ってきてはおらず、複数行の式リストにおけるカンマ省略は現状サポートされていません。なお、関数呼び出しなどにおける末尾カンマの許容については、この提案とは別に後年の SE-0439 で独立に取り込まれています。