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