ScalaのOption(Scala関数型デザインより)
「Scala関数型デザイン&プログラミング」の「第 4 章 例外 を 使わ ない エラー 処理」をやる。
package example sealed trait Option[+A] { def map[B](f: A => B): Option[B] = this match { case None => None case Some(x) => Some(f(x)) } def flatMap[B](f: A => Option[B]): Option[B] = this match { case None => None case Some(x) => f(x) } def flatMap2[B](f: A => Option[B]): Option[B] = this map(f) getOrElse None def getOrElse[B>:A](default: => B): B = this match { case None => default case Some(a) => a } def orElse[B>:A](ob: => Option[B]): Option[B] = this match { case None => ob case _ => this } def orElse2[B>:A](ob: => Option[B]): Option[B] = this map(Some(_)) getOrElse ob def filter(f: A => Boolean): Option[A] = this match { case Some(a) if (f(a)) => this case _ => None } def filter_1(f: A => Boolean): Option[A] = flatMap(a => if (f(a)) Some(a) else None) // Option対応されていない関数(f: A => B)をOptionでくるまれた引数を取れるようにする def lift[A, B](f: A => B): Option[A] => Option[B] = _ map f def map2[A,B,C](a: Option[A], b: Option[B])(f: (A, B) => C): Option[C] = a flatMap(aa => b map (bb => f(aa, bb))) def sequence[A](a: List[Option[A]]): Option[List[A]] = a match { case Nil => Some(Nil) //case h :: t => h flatMap (hh => sequence(t) map (hh :: _)) case Cons(h,t) => h flatMap (hh => sequence(t) map (Cons(hh, _))) //(h flatMap)は、OptionのflatMapを読んでいる } def traverse[A, B](a: List[A])(f: A => Option[B]): Option[List[B]] = a match { case Nil => Some(Nil) case Cons(h,t) => map2(f(h), traverse(t)(f))(Cons(_,_)) } def sequenceViaTraverse[A](a: List[Option[A]]): Option[List[A]] = traverse(a)(x => x) } case class Some[+A](get: A) extends Option[A] case object None extends Option[Nothing] object Option { def calcSlayer(n: Int): Option[Int] = if (n > 5) Some(n) else None def main(args: Array[String]): Unit = { val n = calcSlayer(99) println(s"n:${n}") println(s"n:${n.getOrElse(0)}") println(s"none:${None.getOrElse(0)}") //val m = n flatMap(aa => Some(aa)) val m = n.flatMap(calcSlayer) println(s"m:${m}") println(s"m:${m.getOrElse(0)}") val fnZ = n.lift((a: Int) => a + 100) println(s"fnZ:${fnZ(Some(7))}") val absO = misia.lift(math.abs) println(s"absO:${absO(Some(-11))}") val absb = (Some(-11)).map(math.abs) println(s"absb:${absb}") val misia = None val s1 = Some(1) val s2 = Some(2) val result = misia.map2(s1,s2)((a,b) => a + b) println(s"result:${result}") val lstSome = List(s1,s2) val optLst = misia.sequence(lstSome) println(s"lstSome:${lstSome}") println(s"optLst:${optLst}") val lstNone = List(s1,None,s2) val optNoneLst = misia.sequence(lstNone) println(s"lstNone:${lstNone}") println(s"optNoneLst:${optNoneLst}") } }
Optionのメソッドは、traitに登録する。 なので、Optionのメソッド達を使うためには、インスタンスが必要。
val misia = None val s1 = Some(1) val s2 = Some(2) val result = misia.map2(s1,s2)((a,b) => a + b) println(s"result:${result}")
mapとflatMap
def map[B](f: A => B): Option[B] = this match { case None => None case Some(x) => Some(f(x)) } def flatMap[B](f: A => Option[B]): Option[B] = this match { case None => None case Some(x) => f(x) } def flatMap2[B](f: A => Option[B]): Option[B] = this map(f) getOrElse None
- mapとflatMapは普通にパターンマッチで、NoneとSomeを見てやればよい。
- flatMapの場合は、fがOptionでくるんで返すので、Someの場合、そのままfを呼ぶだけで良い。
getOrElse
def getOrElse[B>:A](default: => B): B = this match { case None => default case Some(a) => a } def orElse[B>:A](ob: => Option[B]): Option[B] = this match { case None => ob case _ => this } def orElse2[B>:A](ob: => Option[B]): Option[B] = this map(Some(_)) getOrElse ob
こちらもパターンマッチでやる。 以下のような形で使う。
println(s"${None.getOrElse(0)}")
filter
def filter(f: A => Boolean): Option[A] = this match { case Some(a) if (f(a)) => this case _ => None } def filter_1(f: A => Boolean): Option[A] = flatMap(a => if (f(a)) Some(a) else None)
- こちらもパターンマッチングで。caseの条件にifを入れるのがミソ。
lift
// Option対応されていない関数(f: A => B)をOptionでくるまれた引数を取れるようにする def lift[A, B](f: A => B): Option[A] => Option[B] = _ map f
lift: Option対応されていない関数(f: A => B)をOptionでくるまれた引数を取れるようにする 「通常の関数をリフトして、Option対応にする。」という
val absO = misia.lift(math.abs) println(s"absO:${absO(Some(-11))}") val absb = (Some(-11)).map(math.abs) println(s"absb:${absb}")
絶対値返す関数(math.abs)をOption対応すると、
実行には、Optionを渡してあげる
mapをそのまま使う場合には、Option値に対して、直接実行する形となる
def lift[A, B](f: A => B): Option[A] => Option[B] = _ map f
liftの式の(_ map f)の「_」を「this」に変えると、その場で実行してしまう形となる。mapを実行した結果のOption[B]の値を返してしまうため、型があわずコンパイルエラーとなる。 「_」であれば、値を返すのではなく、fを「Option[A] => Option[B]」に変換した関数を返す。
map2
def map2[A,B,C](a: Option[A], b: Option[B])(f: (A, B) => C): Option[C] =
a flatMap(aa => b map (bb => f(aa, bb)))
2項関数をOption対応にする。
flatMapとmapの内と外を間違えやすいが、flatMapに渡す関すは、(f: A => Option[B])だから、mapが内。
flatMapとmapで、つつまれたものを、ほどいた後、fに適用する。
sequenceとtraverse
def sequence[A](a: List[Option[A]]): Option[List[A]] = a match { case Nil => Some(Nil) //case h :: t => h flatMap (hh => sequence(t) map (hh :: _)) case Cons(h,t) => h flatMap (hh => sequence(t) map (Cons(hh, _))) //(h flatMap)は、OptionのflatMapを読んでいる } def traverse[A, B](a: List[A])(f: A => Option[B]): Option[List[B]] = a match { case Nil => Some(Nil) case Cons(h,t) => map2(f(h), traverse(t)(f))(Cons(_,_)) } def sequenceViaTraverse[A](a: List[Option[A]]): Option[List[A]] = traverse(a)(x => x)
sequenceは、Optionでくるまれた、リストを入れ替えて、リストをOptionにくるんで返す。
traverseは、リストにする際に関数を適用して、Optionにくるんで返す。
なので、sequenceは、何もしない関数をtraverseに渡せばよい。
case Cons(h,t) => map2(f(h), traverse(t)(f))(Cons(_,_))
traverseの、「map2(f(h), traverse(t)(f))(Cons(,))」は、map2でほどいて、Consするが、リストのtailをtraverse(t)(f)で、再帰して取り出している。