Your browser doesn't support the features required by impress.js, so you are presented with a simplified version of this presentation.

For the best experience please use the latest Chrome, Safari or Firefox browser.

Optionの使い方 入門

by @kazzna

2015-9-21

このスライドのターゲット

  • Option型を使ったことがない人
  • Option型を使い始めたばかりの人

Option型

こんなの
sealed trait Option[+A]
case class Some[A](private val a: A)
extends Option[A]
case object None
extends Option[Nothing]
1. nullを返す代わりにNoneを返す。
例えばDBアクセス。
def selectById(id: String): User
の代わりに、
def selectById(id: String): Option[User]
とすることによって、値がない場合でもnullを返さずにNoneを返せます。
これでnullチェックが楽に?
Optionなし
val user: User =
User.selectById("kazzna")
if (user != null) { ...

Optionあり
val user: Option[User] =
User.selectById("kazzna")
if (!user.isEmpty) { ...
あれ?同じじゃ???
そうですね。
nullチェックがNoneチェックに変わっただけで、本質的には何も変わっていません。
それどころか、UserがOptionに入っているため、以下のようにUserを取り出さないと処理ができません。
val userO: Option[User] =
User.selectById("kazzna")
if (!userO.isEmpty) {
val user: User = userO.get
// userを使った処理
}
とても残念な感じですね。
では、Scalaの構文である、
match 〜 caseを使ってみましょう。
val userO: Option[User] =
 User.selectById("kazzna")
userO match {
case Some(user) => // userを使った処理
case None => None
}
userの取り出しがチェックと同時になった分マシですが、 nullチェックと比べて便利でしょうか?
そうでもない気がしませんか?
2. mapを使う。
case class Some[A](private val a: A)
extends Option[A] {
def map[B](f: A => B): Option[B] =
  Some(f(a))
}
case object None
extends Option[Nothing] {
def map[B](f: A => B): Option[B] =
  None
}
val userO = User.selectById("kazzna")
userO.map(user => /* userを使った処理 */)
これでNoneの時を気にしなくて良くなるので、必要に応じてメソッドチェーンしましょう。
val f: A => B = ???
val g: B => C = ???
val h: C => D = ???
val optionA: Option[A] = ???
optionA.map(f).map(g).map(h)
いつまでmapし続けるかというと、
最後までです。
最後というのはplayで言うなら
Action[Result]までですね。
def index = Action {
val user = User.selectById("kazzna")
/* fとかgとか... */
val a = user.map(f).map(g)
val b = a.map(x => Ok(s"Hello, $x!"))
// b: Option[Result]

 b.getOrElse(NotFound)
}
われわれはユーザーが見つからない時にNotFoundを返すこともやぶさかではない。
3. 2つのOption[User]を組み合わせて使用する。
のは、Scalaに専用の構文がないため省略します。
flatMapで同じことが可能なのでごあんしんください。
タダシイされました。
4. flatMapする。
DBから取得した値って、NULL制約の関係で値がないことってありますよね?
その場合、NULLの可能性がある項目をOptionにしたりします。
case class User(
 id: String,
 name: String, // NOT NULL
 nickName: Option[String],
 premiumKey: Option[Int],
 ...
)
val user = User.selectById("kazzna")
user.map { u =>
 u.nickName.map { n =>
  s"$n is ${u.name}"
 }
}
// => Option[Option[String]]
これをOption[String]にしたいようなときにflatMapが使えます。
case class Some[A](private val a: A)
extends Option[A] {
def flatMap[B](f: A => Option[B]):
  Option[B] = f(a)
}
case object None extends Option[Nothing] {
def flatMap[B](f: A => Option[B]):
  Option[B] = None
}
val user = User.selectById("kazzna")
user.flatMap { u =>
 u.nickName.map { n =>
  s"$n is ${u.name}"
 }
}
// => Option[String]
val user = User.selectById("kazzna")
user.flatMap { u =>
 u.nickName.flatMap { n =>
  u.premiumKey.map { p =>
   s"$n's key is $p."
  }
 }
}
// => Option[String]
5. forを使う。
ScalaのforはflatMapの糖衣構文です。
なので、先ほどと同じ内容が
こう書き換えられます。
val user = User.selectById("kazzna")
for {
 u <- user
 n <- u.nickName
 p <- u.premiumKey
} yield s"$n's key is $p."
// => Option[String]
flatMapの性質上、どれかがNoneだと結果もNoneになります。
nickNameが入力されていない時にはデフォルトの名前を使いたい?
DBをNOT NULLにしてDEFAULT属性をつけましょう。
それが一番です。
どうしてもここだけデフォルト名を使いたい場合は?
val user = User.selectById("kazzna")
for {
 u <- user
 n = u.nickName.getOrElse("友利奈緒")
 p <- u.premiumKey
} yield s"$n's key is $p."
// => Option[String]
<- を使うと、Optionの中身がflatMapされます。
= を使うと、通常の束縛です。
つまりvalと同じ使い方ができます。
val user = User.selectById("kazzna")
for {
 u <- user
 n = u.name
 nn = u.nickName.getOrElse(n)
 p <- u.premiumKey
} yield s"$nn's key is $p."
// => Option[String]

まとめ

おまけ

Optionには「全体が成功」「ひとつでも失敗」の2種類を表すことしかできません。
失敗した個所によってエラー内容を変えるなどの場合はEitherなどを使いましょう。
val user = User.selectById("kazzna")
val res: Either[ERRORS, Result] = for {
 u <- user.toRight(userNotFound).right
 n = u.nickName.getOrElse(u.name)
 pk = u.premiumKey
 p <- pk.toRight(notPrem(u)).right
 ...
} yield Ok(...)
val f: (ERRORS => Result) = ...
res.left.map(f).marge

Use a spacebar or arrow keys to navigate