いい一日はいい睡眠から いい睡眠はいい一日から

読んだ記事のあれこれを書いていきます

2020/03/21

  • リスニング o
  • 英単語10words o
  • Scala o

英単語

  • circulation
    発行部数という意味があることに注意.

Scala

Scalaのエラー

メソッドの返り値として扱う.nullの代替としてOptionが使われる.Optionは値を一つだけ入れることができるコンテナ.

  • Some
  • None

という値が存在する.

getOrElse

例外で動作が停止するのを防げる.

o.getOrElse("empty") // val o: Option[String]でOption[String]の中身がNoneなら"empty"を返す

このように実装できる.

パターンマッチ

Scalaは型のパターンマッチができるため以下のように書ける.

s match {
  case Some(str) => println(str)
  case None => println("None")
}

Optionのコレクションの性質

Some(4).map(_+3)
res0: Option[Int] = Some(7)

mapで関数を適用することができる._ + 4(x) => x + 4と同じ意味._を用いることで引数部分を省略して無名関数を書くことができる.
値がNoneの場合は

val a: Option[Int] = None
a.map(_ + 3)
res0: Option[Int] = None

のようにできるため,「nullの場合は条件を分岐させる」のような処理を記述せずに済む.

fold

foldNoneの場合に実行し,値を返す関数を定義できる.

fold[B](ifEmpty: B)(f: (A) => B): B

以下のようになる.

a.fold(0)(_ * 3) // 前の続きでNoneのa
res1: Int = 0

Optionの入れ子の問題

Optionの適用事例として「存在するなら〜,存在しないなら〜」という風に場合分けが必要となるキャッシュがある.

val a: Option[Int] = Some(4)
a: Option[Int] = Some(4)

val b: Option[Int] = Some(6)
b: Option[Int] = Some(6)

a.map(i1 => b.map(i2 => i1 * i2))
res4: Option[Option[Int]] = Some(Some(24))

のように入れ子になってしまう.これを解消する手段としてflattenメソッドがある.

a.map(i1 => b.map(i2 => i1 * i2)).flatten // Some(24)となる

flatMap

現実のプログラミングではflattenmapの両方を組み合わせて使うことが多い.そのために,flatMapというメソッドがOptionには存在している.

a.flatMap(i1 => v2.map(i2 => i1 * i2))

3つ以上をかける場合,

val a: Option[Int] = Some(1)
a: Option[Int] = Some(1)

val b: Option[Int] = Some(2)
b: Option[Int] = Some(2)

val c: Option[Int] = Some(3)
c: Option[Int] = Some(3)

a.flatMap(i1 => b.flatMap(i2 => c.map(i3 => i1 * i2 * i3)))
res0: Option[Int] = Some(6)

のように書く.が,これだと可読性が低い.

Optionのforの利用

Optionはコレクションと同じようにfor式を扱うことができる.これはfor式がflatMapmapに展開して実行されるため.

val a: Option[Int] = Some(1)
a: Option[Int] = Some(1)

val b: Option[Int] = Some(2)
b: Option[Int] = Some(2)

val c: Option[Int] = Some(3)
c: Option[Int] = Some(3)

for { i1 <- v1
       i2 <- v2
       i3 <- v3} yield i1*i2*i3
res0: Option[Int] = Some(6)

となる.

Either

Optionでエラーを処理しづらい場合

Optionによるエラー処理は,成功したかどうかしかわからずエラー情報を伝えることができないという問題がある.そのため,Optionではエラーの種類が多い場合には利用しづらい.そこで出てくるのがEitherである.

Eitherによるエラー処理

EitherLeftRightをもつ.Leftがエラー,Reftが正常な状態とみなす.Leftに用いるエラー値は代数的データ型(sealed traitとそれを継承したcase classで構成されるデータと型)で定義するとよい.

Eitherの注意

EithermapflatMapを直接持ち得ない.これはmapを使おうとするのに,EitherRightLeftが平等に扱っているため,どちらの値を正しい値として扱うかが決められないため.これを解決するにはどちらを正しい値として優先させるかを決める必要がある.Eitherのメソッドにはleftrightが存在しれいる.これはRightLeftのどちらを優先するのかを決めるメソッド.
例えば,

val v: Either[String, Int] = Right(3) // v: Either[String, Int]
val e = v.right // e: scala.util.Either.RightProjection[String,Int]
e.map(_ * 3) // res0: Product with Serializable with scala.util.Either[String,Int] 

のようになる.Eitherが.rightによってRightProjectionを経由して,.mapを利用するとまたEitherとなる.
for式を使う場合は,

for {
   i1 <- v1.right
   i2 <- v2.right
   i3 <- v3.right
 } yield i1 * i2 * i3

のようにしてやる必要がある.

Try

Tryは正常な値とエラー値のどちらかを表現するデータ型.SuccessFailureが存在し,Eitherとは違って2つの型は平等ではない.またエラー値はThrowableに限定されている.

val v: Try[Int] = Try(throw new RuntimeException("RuntimeException") // v: scala.util.Try[Int] = Failure(java.lang.RuntimeException: RuntimeException)

のようになる.

val v1 = Try(1)
val v2 = Try(2)
val v3 = Try(3)
for {
   i1 <- v1
   i2 <- v2
   i3 <- v3
   } yield i1 * i2 * i3

のようにできる.

Option, Either, Try

Javaでnullを使って処理していたものはOption.
Optionだけでは情報が不足しており,エラー状態が代数的データ型として定義できるものにはEither(復帰可能なエラー)
Javaの例外をどうしても値として扱いたい場合にはTry

toRight

Option型をRightやLeftに変換することができる.Optionでとってきたものがなかった場合,そのエラーを返すこともできる.toRightはSomeであれば中身をRightにくるんで返し,Noneだったら指定した値をLeftにくるんで返す.以下の記事がめっちゃ参考になった.

blog.shibayu36.org