この記事は ウェブクルー Advent Calendar 2019 15日目の記事です。
昨日は @maiunderwood さんの「みんなに聞いてみた!オススメVS Code拡張機能(2019年版)」でした。
はじめに
今度引き継いで運用することになったサービスがScalaを使って作られているとのことで、Scalaを学習している最中です。エラー処理を超入門レベルでまとめてみました。
Option
Scalaは、Javaのクラスを利用でき、その恩恵にあずかることができるのですが、一方でJavaのメソッドを使ったことでnullを受け取ることもありえます。そんな時などに役立つのがOptionです。
Option[A]は、値があるときはSome[A]を返し、値がないときはNoneを返します。
scala> Option("あるよ")
res0: Option[String] = Some(あるよ)
scala> Option(null)
res1: Option[Null] = None
Optionの値をただ取り出したいのなら、getメソッドを使えば良いのですが、
Noneの場合は例外が帰ってきます。
scala> Option("あるよ").get
res2: String = あるよ
scala> Option(null).get
java.util.NoSuchElementException: None.get
at scala.None$.get(Option.scala:349)
at scala.None$.get(Option.scala:347)
... 29 elided
ただ値を取り出したい時にはもっと便利なgetOrElseメソッドがあります。
Noneのときには例外を起こすのではなく、デフォルトの値を得ることができます。
scala> Option("あるよ").getOrElse("ないよ")
res4: String = あるよ
scala> Option(null).getOrElse("ないよ")
res5: String = ないよ
値が入ってる時のみ実行したい関数がある時にはforeachが使えます。
scala> Option("あるよ").foreach(println)
あるよ
scala> Option(null).foreach(println) // 関数は実行されない
パターンマッチでもOptionの値を扱うことができます。
def printNum(num: Option[Int]): Unit = {
num match {
case Some(n) =>
println(s"$n つあるよ")
case None =>
println("ないよ")
}
}
val fruits = new java.util.HashMap[String, Int]
fruits.put("apple", 1)
val appleNum = Option(fruits.get("apple")) // 1
val orangeNum = Option(fruits.get("orange")) // null
printNum(appleNum) // 「1 つあるよ」が出力される
printNum(orangeNum) // 「ないよ」が出力される
Either
OptionはエラーがあってもNoneの値しかもっていないですが、エラー情報も欲しいというときに役立つのがEitherです。
Eitherは、LeftとRightの2つの値を持ちます。
失敗したときにはLeftに情報を格納し、成功したときにはRightに情報を格納します。
思いつかなかったのであまりよくない例ですが、雰囲気だけでも。。。
var total = 99
def checkFruit(fruit: String): Either[String, Int] = {
if (fruit == "banana") {
Right(1) // 成功時
} else {
Left(s"$fruit はポイント対象外です。") // 失敗時
}
}
def processPoints(either: Either[String, Int]) : Unit = {
either match {
case Right(x) => {total = total + x; println(s"今回で${total}ポイントです")}
case Left(x) => println(s"$x 現在は${total}ポイントです")
}
}
processPoints(checkFruit("banana")) // 「今回で100ポイントです」が出力される
processPoints(checkFruit("apple")) // 「apple はポイント対象外です。 現在は100ポイントです」が出力される
foreachを使えば、EitherのRightのときのみ実行したい関数を指定できます。
scala> val right:Either[String,Int] = Right(9)
right: Either[String,Int] = Right(9)
scala> right.foreach(println)
9
scala> val l :Either[String,Int] = Left("lemon")
l: Either[String,Int] = Left(lemon)
scala> l.foreach(println)// Leftのとき関数は実行されない
left.foreachを使えば、EitherのLeftのときのみ実行したい関数を指定できます。
scala> right.left.foreach(println) //Rightの時関数は実行されない
scala> l.left.foreach(println)
lemon
Try
Tryは失敗時に例外を保持するFailureと成功時の結果をもつSuccessの値を持ちます。
FailureはThrowableしか入れられず、TryはNonFatalの例外だけをcatchします。
scala> import scala.util.Try
import scala.util.Try
scala> val seq: Seq[String] = Seq("apple","orange","lemon")
seq: Seq[String] = List(apple, orange, lemon)
// 例外が起きない場合
scala> val fruit:Try[String] = Try(seq(1))
fruit: scala.util.Try[String] = Success(orange)
// 例外が起きる場合
scala> val fruit2:Try[String] = Try(seq(5))
fruit2: scala.util.Try[String] = Failure(java.lang.IndexOutOfBoundsException: 5)
foreachを使えば、Successの時のみ実行したい関数を指定できます。
scala> Try(seq(1)).foreach(println)
orange
scala> Try(seq(5)).foreach(println) // 関数は実行されない
failed.foreachを使えば、Failureの時のみ実行したい関数を指定できます。
scala> Try(seq(1)).failed.foreach(println) // 関数は実行されない
scala> Try(seq(5)).failed.foreach(println)
java.lang.IndexOutOfBoundsException: 5
参考文献
『Scala実践入門』
https://gihyo.jp/book/2018/978-4-297-10141-1
Standard Library
https://www.scala-lang.org/api/current/scala/index.html
Scala研修テキスト
https://scala-text.github.io/scala_text/error-handling.html
最後に
明日は @yagiyuuuuさんです。よろしくお願いいたします!