Swift Digest
SE-0004 | Swift Evolution

Remove the ++ and -- operators

Proposal
SE-0004
Authors
Chris Lattner
Review Manager
Status
Implemented (Swift 3.0)

01 何が問題だったのか

初期のSwiftには、C言語からの継承として前置・後置のインクリメント/デクリメント演算子 ++ / -- が用意されていました。

let a = ++x  // pre-increment:  書き換えた後の x の値を返す
let b = x++  // post-increment: 書き換える前の x の値のコピーを返す
let c = --x  // pre-decrement:  書き換えた後の x の値を返す
let d = x--  // post-decrement: 書き換える前の x の値のコピーを返す

これらはSwiftの初期にあまり深く検討されないまま取り込まれた機能で、改めて見直すと利点よりも欠点の方が大きく、言語を簡潔に保つ観点から正当化しづらい状況でした。

表現上の利点は小さい

++ / -- の主な利点は「戻り値を持ちながら値を書き換えられる」点です。Swiftの += は C と違って Void を返すため、式の中で使うことはできません。とはいえ、x++x += 1 の差はごくわずかで、短縮としての価値はほとんどありません。

C 系言語との連続性というだけの理由

もうひとつの理由は、C / C++ / Objective-C / Java / C# / JavaScript などC系の言語で広く使われており、他言語の利用者にとって馴染みがあることです。ただし同じC系でもPythonのように ++ / -- を持たない言語もあり、「C にあるから Swift にもある」という理由だけでは根拠として弱いものでした。

初学者にとっての学習コストを増やす

Swiftを最初の言語として学ぶ人や、C系以外の言語から入ってくる人にとって、++ / -- は新たに覚えるべき演算子です。x += 1 と大差ない短縮のために、前置・後置の違いや戻り値の違いを説明しなければならないのは、言語の入り口の負担になっていました。

代入系が Void を返すモデルと一貫しない

Swiftでは =+= などの代入系の演算が Void を返すように統一されていますが、++ / -- は値を返す「式」として振る舞い、このモデルから外れていました。

Swiftには代替手段が豊富にある

C言語でよく出てくる「インデックスをインクリメントしながらループする」書き方は、Swiftでは for-in ループや Rangeenumerate(現在の enumerated)、map などで自然に書けます。そもそも ++i を使いたくなる場面自体が、他の言語ほど多くありません。

戻り値を使ったコードが読みにくい

++ / -- の戻り値を実際に利用するコードは、読み手にとって意図が掴みにくく、「賢いけれど理解しづらい」コードを誘発しがちでした。また、foo(++a, a++) のように同じ変数への副作用を1つの式内で重ねる書き方は、評価順序が定義されていても望ましくありません。

適用できる型が狭い

++ / -- が素直に意味を持つのは整数や浮動小数点、あるいはイテレータ的な値に限られ、複素数や行列などには当てはまりません。汎用的な演算子として言語に組み込む価値は小さいものでした。

総じて、「もしこれらの演算子が今なかったとして、Swift 3に新しく追加したいか?」という問いに Yes と答えにくい、というのがこの提案の結論でした。

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

前置・後置のインクリメント/デクリメント演算子 ++ / -- を言語から完全に取り除きます。Swift 2.2 でこれらの演算子を非推奨(deprecated)とし、Fix-itで一般的な書き換えを補助したうえで、Swift 3 で完全に削除されました。

書き換え方

戻り値を使わず副作用だけを目的にしている箇所は、+= 1 / -= 1 に置き換えるのが基本です。

// Before:
x++
++x

// After:
x += 1

戻り値を使っている箇所は、意味に応じて順序を組み立て直します。後置(書き換え前の値を返す)は「値を取り出してから書き換える」、前置(書き換え後の値を返す)は「書き換えてから値を取り出す」という形に展開できます。

// Before: post-increment(書き換え前の値を使う)
let old = x++

// After:
let old = x
x += 1
// Before: pre-increment(書き換え後の値を使う)
let new = ++x

// After:
x += 1
let new = x

イテレータなど ++ を使っていた型

標準ライブラリやユーザー定義の型のうち、advance() 的な意味で ++ を実装していたものも、この変更に合わせて通常のメソッドや += 1 ベースのAPIへ整理されていきます。ループを書く際は、for-inRangeenumerated()map など、Swift本来のイディオムを使う方が簡潔で読みやすくなります。

// Before: C スタイルのループ
for var i = 0; i < xs.count; ++i {
  print(xs[i])
}

// After: for-in とインデックス付き列挙
for x in xs {
  print(x)
}

for (i, x) in xs.enumerated() {
  print(i, x)
}

なお、C スタイルの for ループ自体も別提案(SE-0007)で削除されており、++ / -- の削除と合わせてSwiftのループ表現はコレクション指向のスタイルに集約されました。

この変更で得られるもの

  • 言語の表層がひとつ小さくなり、初学者が覚えるべき構文が減ります。
  • 代入系の演算はすべて Void を返す、というモデルの一貫性が保たれます。
  • x += 1 という明示的な書き方に揃うことで、値の書き換えと値の取り出しが同じ式に混在しにくくなり、読みやすさが上がります。