書いて覚えるSwift入門

第10回 例外を避ける?

この記事を読むのに必要な時間:およそ 3 分

唯一変わったのは,そのすべて

「唯一変わったのは,そのすべて」。iPhone 6sのキャッチコピーですが,むしろそれはSwiftにこそふさわしい一言ではないかというぐらいSwiftは大きく変わりました。前回はそれを広く浅く紹介したのですが,今回からはそれぞれの変化を深く見ていきましょう。

Type?

Swiftの最大の特長は何かと問われたら,筆者はOptional型の多用だと答えます。本連載を最初から追いかけてくださっている読者の皆さんは納得していただけると思いますが,そうではない読者のために,ここで一度おさらいしておきましょう。

次のようなDictionaryがあったとします。

var supportedLanguages = [
    "C" : 1,
    "ObjectiveC" : 2,
]

次はなんとprintするでしょうか?

print(supportedLanguages["C"])

1ではなく,Optional(1)ですね。では次は?

print(supportedLanguages["Swift"])

nilとなります。

今度はOptional(1)ではなく1となります。この挙動を,型に着目して追っていきましょう。まずsupportedLanguagesの型は[String:Int]です。つまりStringを添字にすると,対応するIntが返ってくるデータ型なのですが,その中にないStringを添字には何を返したらよいでしょう? クラッシュするか「何もない」を何らかの形で返すかのどちらかということになります。Swiftが採用したのは後者でした。この「何もない」のがnilで,nilか値を返す」のがOptional型です。つまりsupportedLanguages[k]の型は,IntではなくOptional<Int>つまりInt?ということになります。

ところでSwiftには,enumがあります。Optional型をenumで表現するとどうなるでしょうか? こんな感じでしょうか。

enum Optional<T> {
  case Nil
  case Some(T)
}

Swiftの実装は,まさにそのようになっています。[String, Int]が実はDictionary<String,Int>の構文糖衣であるように,Int?というのはOptional<Int>の構文糖衣に過ぎないのです。

There's more than one way to fail

以上を踏まえて,次を見てみましょう。

var language = "C"
if let i = supportedLanguages[language] {
    print(i)
} else {
    print("Swift is not supported");
}

Optional(1)ではなく,1と表示されます。iの型はInt?ではなくIntで,ifに続く{}の中では100%例外なくiはIntであることが保証されている一方,elseに続く{}の中ではsupported Languages[language]nilだったことが100%例外なく保証されているわけです。これがSwiftにおけるエラー処理の基本でした。Optional(と型変数)導入により,静的な型でもDictionaryのような動的に扱いたい型の扱いが動的言語なみに楽になったのです。

しかし,実際には「うまくいかない」だけではうまくいかないケースは少なくありません。「何がどううまくいかなかった」かによって,処理を変えたいケースも多いのです。たとえば「軽い」エラーならデフォルト値を代わりに使って続行し,「重い」エラーならプログラムを終了する。そういった場合,どうしたらよいのでしょう?

SwiftのOptionalがenumで実装されていることを知っていれば,次のようなSuperOptionalを定義してしまえばその問題は解決しそうです。

enum SuperOptional<E,T> {
    case Error(E)
    case Some(T)
}

func handleSuperOptional<E,T>(so:SuperOptio
nal<E,T>) {
    switch(so) {
    case let .Some(i):
        print(i)
    case let .Error(s):
        print("Error:\(s)")
    }
}

var so:SuperOptional<String, Int> =
.Some(42)
handleSuperOptional(so)
so = .Error("Not a number")
handleSuperOptional(so)

ところが,Swift 1では型変数を複数持つ総称型enumはサポートしていなかったのです図1)。

図1 総称型enumの動作

図1 総称型enumの動作

見てのとおりSwift 2では期待どおり動いていますが,Swift 1.xではコンパイラがクラッシュしてしまいます。

Swift 1におけるOptionalenumで実装されていましたが,Swift 2におけるtrycatchもenumによって実現されています。

著者プロフィール

小飼弾(こがいだん)

1969年生まれ,東京都出身。元ライブドア取締役の肩書きよりも,最近はPokemon GOのガチトレーナーのほうが有名になりつつある……かもしれない永遠のエンジニアオヤジ。

活躍の場はIT業界だけでなく,サブカルからアカデミックまで多方面にわたり,ネットからの情報発信は気の向くまま毎日毎秒! https://twitter.com/dankogai,ニコニコチャンネルは,http://ch.nicovideo.jp/dankogai,blogはhttp://blog.livedoor.jp/dankogai/

当社刊行書籍は『小飼弾のアルファギークに逢ってきた』『小飼弾のコードなエッセイ』など。他にも著書多数。

コメント

コメントの記入