0Pricing
Scala for Backend Engineering & Functional Programming · レッスン

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を返すforeach
  • val =バインディングで中間値を追加する
  • 結果の型は最初のジェネレーターに従う

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フィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. for の脱糖
  2. if によるフィルタリング
  3. 複数のジェネレーター
  4. Option と Future の利用
← Scala for Backend Engineering & Functional Programmingに戻る