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]
例えばDBアクセス。
def selectById(id: String): User
の代わりに、
def selectById(id: String): Option[User]
とすることによって、値がない場合でもnullを返さずにNoneを返せます。
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
}
とても残念な感じですね。
では、Scalaの構文である、
match 〜 caseを使ってみましょう。
val userO: Option[User] =
User.selectById("kazzna")
userO match {
case Some(user) =>
case None => None
}
userの取り出しがチェックと同時になった分マシですが、
nullチェックと比べて便利でしょうか?
そうでもない気がしませんか?
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 => )
これで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")
val a = user.map(f).map(g)
val b = a.map(x => Ok(s"Hello, $x!"))
b.getOrElse(NotFound)
}
われわれはユーザーが見つからない時にNotFoundを返すこともやぶさかではない。
3. 2つのOption[User]を組み合わせて使用する。
のは、Scalaに専用の構文がないため省略します。
flatMapで同じことが可能なのでごあんしんください。
タダシイされました。
DBから取得した値って、NULL制約の関係で値がないことってありますよね?
その場合、NULLの可能性がある項目をOptionにしたりします。
case class User(
id: String,
name: String,
nickName: Option[String],
premiumKey: Option[Int],
...
)
val user = User.selectById("kazzna")
user.map { u =>
u.nickName.map { n =>
s"$n is ${u.name}"
}
}
これを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}"
}
}
val user = User.selectById("kazzna")
user.flatMap { u =>
u.nickName.flatMap { n =>
u.premiumKey.map { p =>
s"$n's key is $p."
}
}
}
ScalaのforはflatMapの糖衣構文です。
なので、先ほどと同じ内容が
こう書き換えられます。
val user = User.selectById("kazzna")
for {
u <- user
n <- u.nickName
p <- u.premiumKey
} yield s"$n's key is $p."
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の中身がflatMapされます。
= を使うと、通常の束縛です。
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に入れたら、最後まで出さない。
- 出さずに操作するためにはmapを使う。
- Option[Option[?]]になりそうなときはflatMap。
- forで書くとシンプル。
おまけ
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