LoginSignup
7
4

More than 3 years have passed since last update.

Bool? 型で ?? を使わないためのテクニック

Last updated at Posted at 2020-06-13

はじめに

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 自体が何であるか」に注目しているのではなく、「anil の場合に条件分岐すべきか」に注目しているからです。表現が回りくどいのです。

もう少し具体的な例を取り上げてみます。
String? 型の値の文字数が0または、nil の場合に空文字として扱いたい場合は次のようになります。

let text: String? = ...
if text?.isEmpty ?? true {
  print("empty or nil.")
}

仕様を何も知らない状態だと、次のようにコードリーディングすると思います。

  1. text に文字列が設定されていれば、 isEmpty が評価される
    1. isEmptytrue ならば print される
    2. isEmptyfalse ならば print されない
  2. text に文字列が設定されていなければ、?? の後ろが評価される
    1. true なので print される
  3. つまり、空白文字列または nil の場合に print される

たった1行で、これだけの判断が必要になります。

こういう記述の仕方に慣れてしまい、プロジェクトのいたるところでこのような記述をしてしまうと、
将来のコードリーディングにおいて、コストになるはずです。

ということで、?? を使わない表現に置き換えてみましょう。

?? を使わないテクニック

引き続き、先程の例を取り上げます。

String? 型の値の文字数が0または nil の場合に空文字として扱いたい場合です。
このとき、?? を使わない表現を考えます。

let text: String? = ...
if text?.isEmpty ?? true {
  print("empty or nil.")
}

この場合は、text は文字数が0または、nil の場合を考えているので、そのことをそのまま条件式にします。コード量は多くなりますが、そのまま読んでいくことができます。

?? を使わない表現:その1
// text は空白または nil
if text?.isEmpty == true || text == nil {
  print("empty or nil.")
}

|| が短絡的に先頭から評価することを利用して、先に nil 判定をしても良いです。そうすれば、== true を省略するという習慣にも合わせる事ができます。

?? を使わない表現:その2
// text は nil または空白
if text == nil || text!.isEmpty {
  print("nil or empty.")
}

Bool? が true, false, nil の三値しかないことに着目すると、false に一致しなければ良いので次のように記述することもできます。コード量は少なくなりますが、否定表現なので先述の表現よりは若干分かりにくかもしれません。

?? を使わない表現:その3
if text?.isEmpty != false { // != で false を否定
    print("empty or nil.")
}

最後に、switch を使ったパターンマッチです。

?? を使わない表現:その4
switch text?.isEmpty {
case true?, nil:
  // true または nil  
  print("empty or nil.")
case false?:
  break
}

次のようなパターンマッチもできますが、若干分かりにくかもしれません。

?? を使わない表現:その5
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 の三値との比較やパターンマッチになります。「対象のオブジェクトが何であるか」だけに集中できるので、コードリーディングの際の負担を減らすことができると思います。

7
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
4