Option
以下の仕様を満たす Map[String, String]
から Option[User]
に変換する optUser
関数を作るとします。
object Main extends App {
case class User(email: String, name: String)
/** Converts Map[String, String] to Option[User].
* @param params
* @return
*/
def optUser(params: Map[String, String]): Option[User] = None
assert(optUser(Map.empty[String, String]) ==
None, "empty parameters")
assert(optUser(Map("email" -> "foo@example.net")) ==
None, "invalid keys")
assert(optUser(Map("email" -> "", "name" -> "Foo")) ==
None, "email is empty")
assert(optUser(Map("email" -> "foo@example.net", "name" -> "")) ==
None, "name is empty")
assert(optUser(Map("email" -> "foo@example.net", "name" -> "Foo")) ==
Some(User("foo@example.net", "Foo")), "valid parameters")
}
for-comprehension を使うと、以下のように簡潔に書けます。
def optUser(params: Map[String, String]): Option[User] =
for {
email <- params.get("email") if !email.isEmpty
name <- params.get("name") if !name.isEmpty
} yield User(email, name)
for-comprehension は
- 先頭を
flatMap[B](f: (A) ⇒ Option[B]): Option[B]
- 以降は
map[B](f: (A) ⇒ B): Option[B]
-
if
はwithFilter(p: (A) ⇒ Boolean): WithFilter
に置き換えてくれますので、以下のコードと同じです。
def optUser(params: Map[String, String]): Option[User] =
params.get("email").withFilter(!_.isEmpty) flatMap { email =>
params.get("name").withFilter(!_.isEmpty) map { name =>
User(email, name)
}
}
無理してパターンマッチで書くと、以下のような冗長なコードになってしまいます。
def optUser(params: Map[String, String]): Option[User] =
params.get("email") match {
case Some(email) =>
if (!email.isEmpty) {
params.get("name") match {
case Some(name) =>
if (!name.isEmpty) Some(User(email, name))
else None
case None => None
}
} else None
case None => None
}
Java 風なコードだと以下のようにも書けます。これはこれで良いような気もします(笑)
def optUser(params: Map[String, String]): Option[User] = {
val email = params.getOrElse("email", "")
val name = params.getOrElse("name", "")
if (!email.isEmpty && !name.isEmpty) Some(User(email, name))
else None
}
Try
Try
でも flatMap
map
を持っているので for-comprehension が使えます。
BankAccountApp.scala
import scala.util.{Try, Success, Failure}
object BankAccountApp {
def withdraw(balance: Int, amount: Int): Try[Int] = {
val x = balance - amount
if (x >= 0) Success(x)
else Failure(new Error("balance is less than " + amount))
}
def main(args: Array[String]) {
val result: Try[String] = for {
a <- Try(args(0).toInt)
b <- Try(args(1).toInt)
balance <- withdraw(a, b)
} yield s"balance: ${balance}"
println(result)
}
}
上記のプログラムは
-
args(0)
: 残高 -
args(1)
: 引出額
を指定して引出後の残高を表示します。
- 正常終了なら
Success(message: String)
- 途中で引数や残高の例外が発生した場合は
Failure(e: Throwable)
が得られます。
$ sbt
> run-main BankAccountApp
Failure(java.lang.ArrayIndexOutOfBoundsException: 0)
> run-main BankAccountApp 100
Failure(java.lang.ArrayIndexOutOfBoundsException: 1)
> run-main BankAccountApp 100 foo
Failure(java.lang.NumberFormatException: For input string: "foo")
> run-main BankAccountApp 100 101
Failure(java.lang.Error: balance is less than 101)
> run-main BankAccountApp 100 100
Success(balance: 0)
> run-main BankAccountApp 100 50
Success(balance: 50)