📘

Scala入門:ふつうのforから“シンタックスシュガーとしてのfor”まで(Scala 2 / REPLで動かす)

に公開

0. 準備:REPLを開く

scala

以降のコードはREPLにコピペして実行できます。出力例も併記します。

1. ふつうのfor(命令的ループ)

1-1. 範囲を回す

scala> for (i <- 1 to 3) println(i)
1
2
3

愚直版(同じ意味):foreachを使う

scala> (1 to 3).foreach(println)
1
2
3

2. “シンタックスシュガーとしてのfor”=for内包表記の基本

yieldを使うと、 「入力コレクションから、変換後の新しいコレクションを返す」 式になります。

2-1. 各要素を2倍にして返す

scala> val xs = List(1, 2, 3)
val xs: List[Int] = List(1, 2, 3)

scala> val doubled = for (x <- xs) yield x * 2
val doubled: List[Int] = List(2, 4, 6)

愚直版(同じ意味):mapで書く

scala> val doubled2 = xs.map(x => x * 2)
val doubled2: List[Int] = List(2, 4, 6)

scala> val xs = List(1,2,3,4,5,6)
val xs: List[Int] = List(1, 2, 3, 4, 5, 6)

3. ガード(if)で絞り込みしてから変換

3-1. 偶数だけ取り出して2倍に

scala> evensTimes2 = for {
     |   x <- xs
     |   if x % 2 == 0
     | } yield x * 2
       ^
       error: not found: value evensTimes2

scala> val evensTimes2 = for {
     |   x <- xs
     |   if x % 2 == 0
     | } yield x * 2
val evensTimes2: List[Int] = List(4, 8, 12)

愚直版(同じ意味):withFilter/filter → map

scala> val evensTimes2b = xs.withFilter(_ % 2 == 0).map(_ * 2)
val evensTimes2b: List[Int] = List(4, 8, 12)

4. 複数ジェネレータ(ネスト)で直積や結合

4-1. 2つのリストの全組合せ(直積)

scala> val as = List("a", "b")
val as: List[String] = List(a, b)

scala> val bs = List(1, 2)
val bs: List[Int] = List(1, 2)

scala> val pairs = for {
     |   a <- as
     |   b <- bs
     | } yield s"$a$b"
val pairs: List[String] = List(a1, a2, b1, b2)

愚直版(同じ意味):flatMapとmap

scala> val pairs = as.flatMap(a => bs.map(b => s"$a$b"))
val pairs: List[String] = List(a1, a2, b1, b2)

5. パターンマッチで要素の形に応じて取り出す

5-1. Mapを回してキーだけ集める

scala> val m = Map("x" -> 1, "y" -> 2)
val m: scala.collection.immutable.Map[String,Int] = Map(x -> 1, y -> 2)

scala> val keys = for {
     |   (k, v) <- m
     | } yield k
val keys: scala.collection.immutable.Iterable[String] = List(x, y)

愚直版(同じ意味):mapで_._1を取る

scala> val keys2 = m.map {case (k, v) => k}
val keys2: scala.collection.immutable.Iterable[String] = List(x, y)

5-2. Optionの中身だけを集める(Someだけ取り出す)

scala> val xs: List[Option[Int]] = List(Some(10), None, Some(30))
val xs: List[Option[Int]] = List(Some(10), None, Some(30))

scala> val onlyValues = for {
     |   Some(x) <- xs // パターンに合わない要素(None)は自動で落ちる
     | } yield x
val onlyValues: List[Int] = List(10, 30)

愚直版(同じ意味):collect

scala> val onlyValues2 = xs.collect {case Some(x) => x }
val onlyValues2: List[Int] = List(10, 30)

6. Optionをつないで「どちらかがNoneならNone」

6-1. 2つのOption[Int]を足す

scala> def plusOptions(a: Option[Int], b: Option[Int]): Option[Int] =
     |   for {
     |     x <- a
     |     y <- b
     |   } yield x + y
def plusOptions(a: Option[Int], b: Option[Int]): Option[Int]

scala> plusOptions(Some(2), Some(3))
val res2: Option[Int] = Some(5)

scala> plusOptions(Some(2), None)
val res3: Option[Int] = None

愚直版(同じ意味):flatMapを手で書く

scala> def plusOptions2(a: Option[Int], b: Option[Int]): Option[Int] =
     |   a.flatMap(x => b.map(y => x + y))
def plusOptions2(a: Option[Int], b: Option[Int]): Option[Int]

scala> plusOptions2(Some(2), Some(3))
val res6: Option[Int] = Some(5)

scala> plusOptions2(Some(2), None)
val res7: Option[Int] = None

7. デシュガリングまとめ

for表記 展開イメージ(Scala 2.13)
for (x <- xs) yield f(x) xs.map(x => f(x))
for (x <- xs if p(x)) yield f(x) xs.withFilter(p).map(x => f(x))
for (x <- xs; y <- ys) yield g(x,y) xs.flatMap(x => ys.map(y => g(x,y)))
for (x <- xs) println(x) xs.foreach(x => println(x))

8. まとめ

  • Scalaのforには2つある:
    1. 命令的なfor(yieldなし)=副作用のためのループ
    2. for内包表記(yieldあり)=map/flatMap/withFilterの糖衣構文
  • 毎回「愚直に」map/flatMapを手でつなぐより、for内包表記の方が読みやすくミスが減る。
  • REPLで試し、「データを作って返す」か「副作用のために回す」かで使い分けよう。

Discussion