🧺
Scala 2 の yield をちゃんと理解する(REPLで動かす実例つき)
0. 準備:REPL を開く
scala
この記事は Scala 2.13 系 を想定。以降は REPL にコピペで動きます。
(出力例は resX: ... = ... の形になります)
1. まずは差を見る:for(副作用) vs for ... yield(結果を返す)
1-1. 副作用だけの for
scala> for (i <- 1 to 3) println(i)
1
2
3
→ 返り値は Unit(結果は返さない)
1-2. 値を返す for ... yield
scala> val r = for(i <- 1 to 3) yield i * 10
val r: IndexedSeq[Int] = Vector(10, 20, 30)
→ 各回の i * 10 を集めて 新しいコレクション(ここでは Vector)を返します。
2. yield の正体(デシュガリング:糖衣構文の展開)
覚え方はこれでOK:
- 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))(filter でも近い)
- 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))
つまり yield は map / flatMap / withFilter の糖衣です。
3. 具体例いろいろ
3-1. 変換:要素を2倍にして返す(= map)
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)
愚直版:
scala> xs.map(_ * 2)
val res1: List[Int] = List(2, 4, 6)
3-2. 抽出+変換:偶数だけ2倍(= withFilter/filter → map)
scala> val xs = List(1,2,3,4,5,6)
val xs: List[Int] = List(1, 2, 3, 4, 5, 6)
scala> val evens2x = for {
| x <- xs
| if x % 2 == 0
| } yield x * 2
val evens2x: List[Int] = List(4, 8, 12)
愚直版:
scala> xs.filter(_ % 2 == 0).map(_ * 2)
val res2: List[Int] = List(4, 8, 12)
3-3. 3-3. 複数ジェネレータ:直積(= flatMap + map)
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)
愚直版:
scala> as.flatMap(a => bs.map(b => s"$a$b"))
val res3: List[String] = List(a1, a2, b1, b2)
3-4. パターンで取り出す(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)
愚直版:
scala> m.map { case (k, _) => k }
val res4: scala.collection.immutable.Iterable[String] = List(x, y)
3-5. Option をつなぐ(どちらか None なら None)
scala> def plus(a: Option[Int], b: Option[Int]): Option[Int] =
| for {
| x <- a
| y <- b
| } yield x + y
def plus(a: Option[Int], b: Option[Int]): Option[Int]
scala> plus(Some(2), Some(3)) // -> Some(5)
val res5: Option[Int] = Some(5)
scala> plus(Some(2), None) // -> None
val res6: Option[Int] = None
愚直版:
scala> def plus2(a: Option[Int], b: Option[Int]): Option[Int] =
| a.flatMap(x => b.map(y => x + y))
def plus2(a: Option[Int], b: Option[Int]): Option[Int]
scala> plus2(Some(2), Some(3)) // -> Some(5)
val res7: Option[Int] = Some(5)
scala> plus2(Some(2), None) // -> None
val res8: Option[Int] = None
4. yield の“返り型”のざっくりルール
- 最初のジェネレータ(x <- xs の xs)がコレクションなら、だいたい同系統のコレクションが返る
- List → List、Vector → Vector、Set → Set(※重複は落ちます)
- Option なら Option が返る
- Range は 2.13 では通常 Vector など IndexedSeq 相当が返る(実装詳細は気にしすぎない)
確認:
scala> (for (x <- List(1,2,3)) yield x).getClass.getName
val res14: String = scala.collection.immutable.$colon$colon
scala> (for (x <- Vector(1,2,3)) yield x).getClass.getName
val res15: String = scala.collection.immutable.Vector1
scala> (for (x <- Set(1,2,2)) yield x).getClass.getName
val res16: String = scala.collection.immutable.Set$Set2
(環境により実クラス名は多少違いますが、概ね上記の性質になります)
5. for の中で一時変数を定義 → yield
scala> val xs = List(1,2,3,4,5)
val xs: List[Int] = List(1, 2, 3, 4, 5)
scala> val result = for {
| x <- xs
| y = x * 3
| if y % 2 == 1
| } yield y
val result: List[Int] = List(3, 9, 15)
愚直版:
scala> xs.map(x => x * 3).filter(_ % 2 == 1)
val res13: List[Int] = List(3, 9, 15)
6. よくある勘違い・落とし穴
- Q. yield は関数の return と同じ?
→ 違います。 yield は「各イテレーションの式の結果を集める」ための合図。
関数から抜ける return とは別物です。 - Q. for に yield を付け忘れると?
→ Unit になってしまう(値が返らず副作用だけ)。
「結果を得たいのに空っぽ…」というバグの定番。 - Q. Set や Map に yield したら?
→ その性質(重複削除・キー一意)が効きます。
例:Set(1,1,2).map(_*2) は Set(2,4)。 - Q. 複数ジェネレータの順番は影響する?
→ 影響します。 for { a <- as; b <- bs } は as.flatMap(a => bs.map(...)) と同じ。
先に外側 as が来ます。
7. まとめ(覚え方)
- for ... yield は 結果を返す for。
map/flatMap/withFilter の糖衣構文(読みやすさのための記法)。 - 副作用のために回すだけなら yield なし(または foreach)。
- Option など「つないで失敗を伝播」する処理も for で読みやすく書ける。
Discussion