読者です 読者をやめる 読者になる 読者になる

Swiftのoptionalが妙にしっくりきた理由

AdventCalendar2015

以前はRubyを一番書いていたのですが、最近Swift書いています。 Optional型がすごく素敵な仕組みだと思いました。 基本的にnilの乱用を防ぐ仕組みなのですが、僕はRubyではnilに苦しめられた方なので、すごくOptionalがしっくりきました。

Rubyでのnil対策

Rubyでは型の概念はなく、変数にnilを渡すことができます。 しかも、nilだけでなくどんなクラスも宣言なしに変数に代入することができるため、開発時に変数にどんなクラスが入るかを限定することはできません。

Rubyではto_i、to_sなどのクラス変換メソッドを全てのクラス(nilも)の親クラスであるObjectクラスに実装することにより、nil対策をしています。 例えば変数の値がStringであることを想定している場合、変数利用側でto_sをかけてやることによってStringであることを確実にします。

2.2.1 :001 > nil.to_s
=> "" 
2.2.1 :002 > "aaa".to_s
=> "aaa" 
2.2.1 :003 > 1.to_s
=> "1" 
2.2.1 :004 > [1,2,3].to_s
=> "[1, 2, 3]"

このアプローチの問題点として、上記のようにto_sを挟むとフローが統一できていいんだけれど、その後ifがある場合に意図せずnilの場合と""(空文字列)の場合のフローが同じになったりします。

また、nilを防ぐためのこのアプローチの副作用として、サードパーティ製コードや、自作のコードに独自のto_sが実装されていると想定外の動きをしてしまうことがあるので、現実的には変数使う側が使われる側のクラスの知識を持たなくてはならなくなったり、理想と逆行してしまうことがあります。

問題点をまとめると

  1. nilの場合に何が起きるかわかりづらい。プログラマーが神経すり減らして自力で処理する必要がある。
  2. これを防ぐto_sなど使うと、すべてのクラスのto_sの実装を心配する必要がある

このrubyでの経験があった上でSwiftに入ったため、Optionalが非常にしっくりきました。

Swiftでのnil対策

SwiftではOptional型という型を導入することによって、その他の型にはnilを禁止しています。

では実際にこのOptional型を使うときにはどうするかというと、以下の方法でOptional型を剥いで(unwrapして)その中の元々の型をむき出しにしてからじゃないとつかえません。

Optional Binding

nilかどうかをチェックして、nilでない場合のみifの中の処理をする 上記のnilの場合に何が起きるかわかりづらい問題を解決しています。

var myClass: MyClass? = nil
if let nonNilMyClass = myClass {//myClass == nilの場合は入らない
  nonNilMyClass.myMethod()
}

ただ、これだけだとnilが入る可能性のある変数は常にチェックしなければならず、面倒になります。 この面倒を解決する方法として、以下の書き方もできます。

Optional Chaining

nilの場合はなにもしない書き方

var myClass: MyClass? = nil
myClass?.myMethod()

Rubyの場合と比べていいところは、nilの場合、nilでない場合のフローを明示的に表現できることによって、開発者の脳味噌のコストを減らせることにあります。 また、そもそもGenericsなどの仕組みを使わないかぎり、変数に想定外のクラスが入ってくることもないため、to_sの場合のようにいろいろなクラスのことを想定する必要もありません。

こういった理由によって、SwiftのOptionalが素敵だと思っています。

今回は「nilを防ぐ仕組み」という面にだけ注目しましたが、そもそも言語の思想の違いがあるのでどちらがいいとは言えません。 Rubyの理想形を求める姿勢は最高にクールです。 そもそもレベルの高いプログラマーが操れば、テストなどを併用することによって上記の問題もさらっと解決できると思います。 ですがその理想ゆえに、僕のようなレベルがあまり高くないプログラマーにとっては(または全員のレベルが高いことが保証されないプロジェクトにとっては)運用のコストが高くなることもまた事実だと思います。