Swift Digest
SE-0024 | Swift Evolution

Optional Value Setter ??=

Proposal
SE-0024
Authors
James Campbell
Review Manager
Doug Gregor
Status
Rejected

01 何が問題だったのか

Swift では、オプショナルな変数に「値がまだ無ければデフォルト値をセットし、すでに値があればそのまま残す」という更新をしたい場面がしばしばあります。現在この操作は、?? 演算子と代入を組み合わせて次のように書きます。

really.long.lvalue[expression] = really.long.lvalue[expression] ?? ""

左辺の繰り返しが冗長になる

?? は「左辺が nil ならば右辺」という値を返す演算子なので、「nil のときだけ自身に代入したい」場合は左辺をもう一度書く必要があります。左辺が really.long.lvalue[expression] のように長いパスや添字アクセスを含むと、同じ式を2回書くことになり、読みづらさとタイプミスの温床を生みます。

単純な代入と違い、+=*= のような複合代入演算子が左辺を1回だけ書けば済むのと比べても、この「nil のときだけ埋める」というよく使うパターンだけが長い式を強いられていました。

他言語からの移行でも目立つパターン

Ruby には同等の意味を持つ ||= 演算子があり、同じ発想の糖衣構文としてよく使われています。提案者は、このパターンを1つの演算子で書けるようにすることで、他言語から来たプログラマの学習コストも下げられると主張していました。

本提案は、このパターン専用の演算子「Optional Value Setter」として ??= を追加し、左辺が nil のときだけ右辺を代入する操作を1行で表せるようにしよう、というものでした。

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

この提案は Rejected(却下) となりました。したがって、Swift に ??= 演算子は導入されていません。オプショナルが nil のときだけ値を入れたい場合は、現在も ?? と代入を組み合わせるか、明示的に if で分岐するのが基本です。

提案されていた内容(却下されたもの)

仮に採択されていれば、オプショナルな左辺に対して次のように書けるようになる予定でした。

var itemsA: [Item]? = nil
var itemsB: [Item]? = [Item()]

itemsA ??= []  // itemsA は nil だったので [] がセットされる
itemsB ??= []  // itemsB はすでに値を持っているので変化しない

意味としては、おおむね次のコードと等価です。

if really.long.lvalue[expression] == nil {
    really.long.lvalue[expression] = ""
}

提案では、左辺がすでに値を持っている(.some)のときは代入自体が起きず、willSet / didSet も呼ばれないべきだ、という挙動も想定されていました。

却下された理由

レビューでは、??= という新しい演算子を入れるほどの利得が明確でない、という判断に至りました。x = x ?? y という現行の書き方でも意味は十分読み取れますし、左辺が本当に長くて繰り返したくないケースは、ローカル変数に束ねるか if 文で明示的に書いたほうが意図が伝わりやすい、という整理です。

演算子を1つ増やすと、言語全体として覚えるべき記号が増え、??=??= の細かな違いをその都度読み解く負担も生じます。Swift は「便利そうな糖衣構文を足していく」よりも「既存の仕組みで十分読み書きできるならそれに任せる」という方針を取っており、本提案はそのラインを越えるだけの価値が認められないと判断されて却下されました。

実務上のスタンス

現在のSwiftでは、オプショナルに「値が無ければデフォルトを入れる」操作は次のいずれかで表現します。

// 1. ?? と代入で書く
value = value ?? defaultValue

// 2. if で明示的に分岐する
if value == nil {
    value = defaultValue
}

左辺が極端に長くなる場合は、中間変数に束ねて読みやすくするのが定石です。いずれにせよ、専用の演算子が無くても意図は十分表現できるため、??= の不在が実用上の障害になる場面はほとんどありません。