for の脱糖
map と flatMap へ変換します。
「for の脱糖」はCoddyKit上の無料Scala for Backend Engineering & Functional Programmingレッスンです。 これはレッスン1/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはScala for Backend Engineering & Functional Programming学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Scala for Backend Engineering & Functional Programmingコースには全4レッスンが含まれています。
for-comprehensionとは
Scalaのfor-comprehensionは、コレクションやその他のコンテナ型を扱うための特別な構文です。命令型のループのように見えますが、実際にはコンパイラーがmap、flatMap、filter、foreachの呼び出しに書き換える糖衣構文です。
糖衣構文の展開方法を理解すると、カスタム型を含むあらゆるfor-comprehensionの動作を考えやすくなります。
object Main {
def main(args: Array[String]): Unit = {
val result = for (x <- List(1, 2, 3)) yield x * 10
println(result)
}
}単一のジェネレーターはmapになる
for-comprehensionにジェネレーターが1つだけあり、yieldを使う場合、コンパイラーは単一のmap呼び出しに書き換えます。
for (x <- xs) yield f(x)- は
xs.map(x => f(x))になります
object Main {
def main(args: Array[String]): Unit = {
val a = for (x <- List(1, 2, 3)) yield x + 1
val b = List(1, 2, 3).map(x => x + 1)
println(a == b)
}
}yieldキーワード
yieldキーワードは、結果を収集して同じ種類の新しいコレクションにするようScalaに指示します。
yieldがない場合、for-comprehensionは各要素に対して副作用を実行し、Unitを返します(foreachになります)。
object Main {
def main(args: Array[String]): Unit = {
// No yield: side effect only
for (x <- List("a", "b")) println(x)
// With yield: builds a new List
val upper = for (x <- List("a", "b")) yield x.toUpperCase
println(upper)
}
}2つのジェネレーターはflatMap + mapになる
2つ以上のジェネレーターがある場合、外側のジェネレーターはflatMapに、最も内側のジェネレーターはmapになります。
for (x <- xs; y <- ys) yield f(x, y)- は
xs.flatMap(x => ys.map(y => f(x, y)))になります
object Main {
def main(args: Array[String]): Unit = {
val pairs = for {
x <- List(1, 2)
y <- List("a", "b")
} yield (x, y)
println(pairs)
}
}同等のflatMapを確認する
ここでは同じロジックを両方の方法で記述します。両者が等しいことを示すことで、for-comprehensionには魔法がなく、読みやすさを向上させるだけだと分かります。
object Main {
def main(args: Array[String]): Unit = {
val sugar = for {
x <- List(1, 2)
y <- List(10, 20)
} yield x + y
val desugared = List(1, 2).flatMap(x => List(10, 20).map(y => x + y))
println(sugar)
println(sugar == desugared)
}
}外側のジェネレーターでflatMapを使う理由
外側の各要素は、内側の結果からなるコレクション全体を生成します。外側のジェネレーターにmapを使うと、List(List(...), List(...))のようなネストしたコレクションになります。
flatMapはネストしたリストをフラット化し、1つの平坦な結果にします。
object Main {
def main(args: Array[String]): Unit = {
val nested = List(1, 2).map(x => List(10, 20).map(y => x + y))
val flat = List(1, 2).flatMap(x => List(10, 20).map(y => x + y))
println(nested)
println(flat)
}
}yieldのないforはforeachになる
yieldのないforループはforeachに書き換えられます。本文を副作用のために実行し、Unitを生成します。
for (x <- xs) doSomething(x)- は
xs.foreach(x => doSomething(x))になります
object Main {
def main(args: Array[String]): Unit = {
val r: Unit = for (x <- List(1, 2, 3)) print(x + " ")
println()
println("Return type is Unit")
}
}結果の型は最初のジェネレーターに従う
結果の型は、最初のジェネレーターのコレクションによって決まります。Listに対するforはListを、Setに対するforはSetを、Optionに対するforはOptionを返します。
object Main {
def main(args: Array[String]): Unit = {
val fromList = for (x <- List(1, 2, 2, 3)) yield x
val fromSet = for (x <- Set(1, 2, 2, 3)) yield x
println(fromList)
println(fromSet)
}
}=で値を束縛する
for-comprehensionの中では、=を使って中間値を束縛できます。再計算を避け、読みやすさを向上できます。
これは、追加の値をタプルにして引き回すmapに展開されます。
object Main {
def main(args: Array[String]): Unit = {
val result = for {
x <- List(1, 2, 3)
doubled = x * 2
} yield doubled + 1
println(result)
}
}map/flatMapを持つ任意の型で動作する
糖衣構文の展開がmapとflatMapだけに依存するため、for-comprehensionはそれらのメソッドを定義する任意の型で動作します。たとえばOption、Either、Try、Future、独自のクラスなどです。
object Main {
def main(args: Array[String]): Unit = {
val combined = for {
a <- Some(2)
b <- Some(3)
} yield a * b
println(combined)
}
}本当の利点は読みやすさ
深くネストしたflatMap/mapのチェーンと、すっきりしたfor-comprehensionを比べてみましょう。どちらも同じコードにコンパイルされますが、forのほうはレシピのように上から下へ読めます。
object Main {
def main(args: Array[String]): Unit = {
val chained = List(1, 2).flatMap(a => List(3, 4).map(b => a * b))
val readable = for {
a <- List(1, 2)
b <- List(3, 4)
} yield a * b
println(chained == readable)
}
}理解度チェック
2つのジェネレーターとyieldを含むfor-comprehensionは、コンパイラーによってどのように展開されますか。
まとめ
for-comprehensionがどのように展開されるかを学びました。
- 1つのジェネレーター +
yield→map - 複数のジェネレーター +
yield→flatMap(外側)+map(内側) yieldなし →Unitを返すforeachval =バインディングで中間値を追加する- 結果の型は最初のジェネレーターに従う
mapとflatMapを持つ任意の型をfor-comprehensionで使用できます。
よくある質問
「for の脱糖」レッスンは無料ですか?
はい。「for の脱糖」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Scala for Backend Engineering & Functional Programmingコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Scala for Backend Engineering & Functional Programmingコースには全4レッスンが含まれています。
「for の脱糖」で何を学びますか?
map と flatMap へ変換します。 ブラウザで直接実行するハンズオンコードでScala for Backend Engineering & Functional Programmingを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Scala for Backend Engineering & Functional Programmingを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのScala for Backend Engineering & Functional Programmingは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン1/4です。
「for の脱糖」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このScala for Backend Engineering & Functional Programmingレッスンでコードを書いて実行できますか?
はい。すべてのScala for Backend Engineering & Functional Programmingレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。