はじめに
Swift の入門的な記事です。
次のようなコードから ??
( Nil-Coalescing Operator )をなくすためのテクニックについて扱います。
if a ?? true {
doSomething()
}
前置き・動機づけ
多くのプログラミング言語では、条件分岐における == true
を習慣的に嫌います。
if a == true {
print("a is true")
}
そして、次のような記述の方が簡潔で好まれています。
if a {
print("a is true")
}
しかしながら、Optional 型の場合は、このような記述はできません。
Bool? と Bool は異なる型であり、Bool? は条件式として評価ができないからです。
var a: Bool? = ...
if a { // コンパイルエラー
}
そこで、習慣的には嫌われている == true
が役に立ちます。
二項演算子 ==
は戻り値として Bool 型の値を返すのでコンパイルエラーになりません。
var a: Bool? = ...
if a == true {
print("a is true")
}
if a == false {
print("a is false")
}
if a == nil {
print("a is none")
}
このような場合に、??
( Nil-Coalescing Operator )を使うことでも Bool 型の値を返すことができますが、二項演算子 ==
を用いた方が直感的で可読性が高いと思います。
if a ?? false {
print("a is true")
}
if a == true {
print("a is true")
}
??
の方が直感的でない理由は、「a
自体が何であるか」に注目しているのではなく、「a
が nil
の場合に条件分岐すべきか」に注目しているからです。表現が回りくどいのです。
もう少し具体的な例を取り上げてみます。
String? 型の値の文字数が0または、nil
の場合に空文字として扱いたい場合は次のようになります。
let text: String? = ...
if text?.isEmpty ?? true {
print("empty or nil.")
}
仕様を何も知らない状態だと、次のようにコードリーディングすると思います。
-
text
に文字列が設定されていれば、isEmpty
が評価される-
isEmpty
がtrue
ならばprint
される -
isEmpty
がfalse
ならばprint
されない
-
-
text
に文字列が設定されていなければ、??
の後ろが評価される-
true
なのでprint
される
-
- つまり、空白文字列または
nil
の場合にprint
される
たった1行で、これだけの判断が必要になります。
こういう記述の仕方に慣れてしまい、プロジェクトのいたるところでこのような記述をしてしまうと、
将来のコードリーディングにおいて、コストになるはずです。
ということで、??
を使わない表現に置き換えてみましょう。
??
を使わないテクニック
引き続き、先程の例を取り上げます。
String? 型の値の文字数が0または nil
の場合に空文字として扱いたい場合です。
このとき、??
を使わない表現を考えます。
let text: String? = ...
if text?.isEmpty ?? true {
print("empty or nil.")
}
この場合は、text
は文字数が0または、nil
の場合を考えているので、そのことをそのまま条件式にします。コード量は多くなりますが、そのまま読んでいくことができます。
// text は空白または nil
if text?.isEmpty == true || text == nil {
print("empty or nil.")
}
||
が短絡的に先頭から評価することを利用して、先に nil
判定をしても良いです。そうすれば、== true
を省略するという習慣にも合わせる事ができます。
// text は nil または空白
if text == nil || text!.isEmpty {
print("nil or empty.")
}
Bool? が true
, false
, nil
の三値しかないことに着目すると、false
に一致しなければ良いので次のように記述することもできます。コード量は少なくなりますが、否定表現なので先述の表現よりは若干分かりにくかもしれません。
if text?.isEmpty != false { // != で false を否定
print("empty or nil.")
}
最後に、switch
を使ったパターンマッチです。
switch text?.isEmpty {
case true?, nil:
// true または nil
print("empty or nil.")
case false?:
break
}
次のようなパターンマッチもできますが、若干分かりにくかもしれません。
switch text?.isEmpty {
case false?:
break
case _:
// true または nil
print("empty or nil.")
}
通常は上記のような表現は記述しませんが、複数の変数を扱う場合に便利です。タプルでパターンマッチすれば上述と同じように記述ができます。
let text1: String? = ...
let text2: String? = ...
switch (text1?.isEmpty, text2?.isEmpty) {
case (false?, _), (_, false?):
break
case _:
print("both texts are empty or nil.")
}
以上のように Bool? 型で ??
をやめると、true
, false
, nil
の三値との比較やパターンマッチになります。「対象のオブジェクトが何であるか」だけに集中できるので、コードリーディングの際の負担を減らすことができると思います。