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

新人SEの学習記録

14年度入社SEの学習記録用に始めたブログです。気づけば社会人3年目に突入。

学習記録:Scala関数型デザイン

第4章:例外を使わないエラー処理(続き)

Optionデータ型(続き)

Optionの基本関数を使用するシナリオ

Optionのそれぞれの基本関数を使用する状況についてガイドラインを示す。
これらの関数は練習すれば使いこなせるようになるが,ここでは基本的な知識を身につけることを目標とする。

map関数は,Optionに結果が存在する場合に,そのmap変換に使用できる。
これについては,エラーが発生していないという前提で計算を進めるものと考えることができる.

// 従業員クラス
case class Employee(name: String, dept: String)

// 従業員を名前で検索する関数
def lookupByName(name: String): Option[Employee] = ...

// Joeが従業員の場合,Some(Joeの部署)
// Joeが従業員でない場合,None
val joeDepartment: Option[String] = lookupByname("Joe").map(_.dept)

lookupByName("Joe")はOption[Employee]を返す。
これをmapで変換し,部署を表すOption[String]を抽出する。
lookupByName("Joe")がNoneを返すとしても,そのことをチェックする必要はない。
エラーは発生していないものとしてmapの引数内で処理を続け,
map関数内でNoneを返す(その際,_.dept関数を呼び出さない)という処理になっている。

仮に,Joeが存在しない場合にデフォルトの部署を取得したい場合,
getOrElseを使用して以下のように書ける。

// Joeが従業員の場合,Joeの部署
// Joeが従業員でない場合,Default Dept
lookupByName("Joe").map(_.dept).getOrElse("Default Dept.")

flatMapも同様だが,結果を変換する関数自体が失敗する可能性がある。

// Joeに上司が存在する場合Some(manager)
// Joeに上司がいないか,Joeが従業員でない場合はNone
lookupByName("Joe").flatMap(_.manager)

成功値が指定された述語条件とマッチしない場合,filterを使って成功を失敗に変換できる。
map, flatMap, filterの呼び出しを通じてOptionを変換し,エラー処理を最後に実行するのが一般的なパターン。

val dept: String =
  lookupByName("Joe").    // Joeの従業員情報Option[Employee]を取得。Joeがいない=Noneの可能性あり
  map(_.dept).                     // Joeの部署Option[String]を取得。
  filter(_ != "Accounting").    // Joeの部署がAccountingだったらNoneになる
  getOrElse("default dept.")  // どこかでNoneになればdefault deptに,そうでなければJoeの部署名が取得できる

上の例では,MapにJoeというキーが存在しない場合,あるいはJoeの部署がAccountingの場合Default Deptを返す。
今回はOption[String]をStringに変換するためにgetOrElseを使っている。
orElseもgetOrElseと似ているが,orElseはOptionが未定義の場合に別のOptionを返す。
失敗する可能性のある計算を数珠つなぎにし,1つ目が成功しなかった場合に2つ目を試すようなときには,orElseを使うと便利。

このように,高階関数を使うと例外を使ったときと同じようにエラー処理ロジックを一本化できる。
計算のステージごとにNoneをチェックする必要はなく,様々な変換をした後に準備ができたところでNoneに対処すれば良い。
また,Option[A]はAとは異なる型であり,Noneの可能性を明示的に先送りもしくは処理することを忘れたら,
コンパイラが警告してくれるため,より安全である。