for式にOption[T]を投入する

Option[T]をfor式に入れたらうまくいったのでそれについて。

before

dialog.open() match { // swtのファイルダイアログ呼び出し
  case null =>
  case path =>
  val file=new java.io.File(path);
  loaders.find(_ accept file) match { // Array[Loader]#find(f:Loader=>Boolean):Option[T]
    case None =>
    case Some(loader) =>
    loader.load(file) match { // def load(file:java.io.File):Option[Model]
      case None =>
      case Some(model) =>
      builders.find(_ accept model) match { // def accept(model:Model):Boolean
        case None =>
        case Some(builder) =>
        builder.build(model) match { // def build(model:model):Option[Drawable]
          case None =>
          case Some(drawable) =>
          println("success: "+file+" !!")
        }
      }
    }
  }
}

何をやっているかというと、swtのファイルダイアログ呼び出しを基点に
nullチェックをやって、Option[T]を返す関数を適用してSome(hoge)になった場合にだけ処理を先に進めているわけです。
素直に、match式でOption[T]の中身を取り出して処理しております。
None側は捨てているけど、警告が出るので必要です。


既にOption[T]を変数に代入してからif文で中身のチェックをするmatch式を使わない↓のコード

// scalaでやることはないと思うがこんなの?
val opt1:Option[hoge1]=func1();
if(opt1.isDefined){
    val opt2:Option[hoge2]=func2(opt1.get);

よりは、成否のチェックと変数束縛を一文でやっているので悪くないと思っておりました。
しかし、Option[T]がfor式に適用できる要件を満たしているので上記のコードは
下記のようにできるのでした。

after

for(
  path <- Nullable(dialog.open());
  file=new java.io.File(path);
  loader <- loaders.find(_ accept file);
  model <- loader.load(file);
  builder <- builders.find(_ accept model);
  drawable <- builder.build(model)){
  println("success: "+file+" !!");
}

ネストとNone側が無くなってシンプルになりました。


Nullableの定義

// どっかに同様のものがありそうな気がするが・・・
object Nullable {
  def apply[T](nullable:T):Option[T]=nullable match {
    case null => None
    case _ => Some(nullable)
  }
}

要するにOption[T]のSome側にしか興味が無い場合は、match式を使わずともfor式で中身を取れますよ、ということでした。
ここで、Option[T]は一個しかもののはいらないList[T]だと考えるとfor式で中身が取れることは納得しやすい。
Option[T]の真の嬉しさは、for式に放り込めることにあるようだ。


小さい例(Array[T]#find(f:T=>Boolean):Option[T])だと、

val list=List("a", "b", "c");

list.find(_ == "a") match {
  case None => println("not found !")
  case Some(found) => println(found)
}

for(found <- list.find(_ == "a")){
  println(found)
}

さらに

list.find(_ == "a").foreach{ found => 
  println(found) 
}

がだいたい同じとなります。


複数の<-が連鎖するメカニズムはちゃんと理解していないが、flatMapに渡す高階関数の返り値をOption[T]にするのがひとつのコツになっているようだ。