Help us understand the problem. What is going on with this article?

ScalaのOption/Either/Try超入門

この記事は ウェブクルー 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さんです。よろしくお願いいたします!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした