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

Either の合成

map と flatMap です。

「Either の合成」はCoddyKit上の無料Scala for Backend Engineering & Functional Programmingレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはScala for Backend Engineering & Functional Programming学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Scala for Backend Engineering & Functional Programmingコースには全4レッスンが含まれています。

計算を組み合わせる

実際のプログラムでは、パース、検証、検索、計算といった失敗する可能性のある複数のステップを連鎖させます。Either を使うと、最初の失敗でチェーン全体を短絡させながら、これらを組み合わせられます。

使用するツールは map、flatMap、for 内包表記です。

flatMap で Either を連鎖させる

flatMap は、それ自体が Either を返す関数を適用します。レシーバーが Left の場合、関数は呼び出されず、Left がそのまま伝播します。

object Main {
  def positive(n: Int): Either[String, Int] =
    if (n > 0) Right(n) else Left("not positive")

  def main(args: Array[String]): Unit = {
    val r = Right(5).flatMap(positive)
    val l = Right(-1).flatMap(positive)
    println(r)
    println(l)
  }
}

短絡評価

複数の flatMap 呼び出しを連鎖させると、最初の Left がすべての処理を停止させます。後続のステップはスキップされ、そのエラーが最終結果になります。

object Main {
  def step(label: String, n: Int): Either[String, Int] = {
    println(s"running $label")
    Right(n + 1)
  }

  def main(args: Array[String]): Unit = {
    val result = Left("early error").asInstanceOf[Either[String, Int]]
      .flatMap(n => step("A", n))
      .flatMap(n => step("B", n))
    println(result)
  }
}

Either に対する for 内包表記

Either は右偏向なので、for 内包表記で使用できます。各 <- は Right の値を取り出し、Left があれば内包表記を停止して、その値が結果になります。

object Main {
  def parse(s: String): Either[String, Int] =
    s.toIntOption.toRight(s"bad: $s")

  def main(args: Array[String]): Unit = {
    val sum = for {
      a <- parse("3")
      b <- parse("4")
    } yield a + b
    println(sum)
  }
}

失敗する for 内包表記

内包表記のいずれかのステップが Left を生成すると、式全体がその Left になり、後続のステップは実行されません。

object Main {
  def parse(s: String): Either[String, Int] =
    s.toIntOption.toRight(s"bad: $s")

  def main(args: Array[String]): Unit = {
    val sum = for {
      a <- parse("3")
      b <- parse("oops")
      c <- parse("5")
    } yield a + b + c
    println(sum)
  }
}

検証を連鎖させる

現実的なパイプラインでは、文字列をパースし、ビジネスルールを確認してから変換します。各段階が Either を返します。

object Main {
  def parse(s: String): Either[String, Int] = s.toIntOption.toRight("not a number")
  def checkRange(n: Int): Either[String, Int] =
    if (n >= 1 && n <= 100) Right(n) else Left("out of range")

  def process(s: String): Either[String, Int] =
    parse(s).flatMap(checkRange).map(_ * 10)

  def main(args: Array[String]): Unit = {
    println(process("7"))
    println(process("500"))
    println(process("x"))
  }
}

map と flatMap の比較

関数が通常の値を返す場合は map を使用します。別の Either を返す場合は flatMap を使用し、Either[String, Either[String, Int]] のような入れ子を避けます。

@main def run(): Unit = {
  val withMap: Either[String, Int] = Right(2).map(_ + 1)
  val nested: Either[String, Either[String, Int]] = Right(2).map(n => Right(n + 1))
  val flat: Either[String, Int] = Right(2).flatMap(n => Right(n + 1))
  println(withMap)
  println(nested)
  println(flat)
}

独立した値を組み合わせる

各ステップが前のステップに依存しない場合でも、for 内包表記を使用できます。最終的な yield が実行されるには、すべてのステップが成功する必要があります。

object Main {
  def parse(s: String): Either[String, Int] = s.toIntOption.toRight(s"bad: $s")

  def main(args: Array[String]): Unit = {
    val combined = for {
      x <- parse("10")
      y <- parse("20")
      z <- parse("30")
    } yield List(x, y, z).sum
    println(combined)
  }
}

leftMap 形式のエラー変換

標準ライブラリの Either には leftMap がありませんが、swap.map(...).swap を使うか、fold の内部でマッピングしてエラーを変換できます。これにより、パイプライン全体でエラー型を一貫させられます。

@main def run(): Unit = {
  val e: Either[String, Int] = Left("low-level error")
  val mapped = e.swap.map(msg => s"context: $msg").swap
  println(mapped)
}

すべてを組み合わせる

2 つの数値をパースして割り算を行い、失敗した場合は型付きエラーとして報告する、小さな計算機のパイプラインです。

object Main {
  def parse(s: String): Either[String, Int] = s.toIntOption.toRight(s"bad number: $s")
  def divide(a: Int, b: Int): Either[String, Int] =
    if (b == 0) Left("division by zero") else Right(a / b)

  def calc(x: String, y: String): Either[String, Int] =
    for {
      a <- parse(x)
      b <- parse(y)
      r <- divide(a, b)
    } yield r

  def main(args: Array[String]): Unit = {
    println(calc("20", "4"))
    println(calc("20", "0"))
    println(calc("x", "4"))
  }
}

これが重要な理由

Either を組み合わせると、レール指向プログラミングを実現できます。正常系は Right を通って進み、エラーが発生すると Left の線路へ分岐して残りの処理を迂回します。例外も null チェックもなく、値だけで処理できます。

確認問題

Either の組み合わせについての理解を確認しましょう。

まとめ

Either を組み合わせる方法を学びました。

  • flatMap は失敗するステップを連鎖させ、最初の Left で短絡します。
  • for 内包表記を使うと、複数ステップのパイプラインを読みやすく記述できます。
  • 通常の結果には map を、Either を返す関数には flatMap を使用します。
  • swap.map(...).swap でエラーを変換します。

よくある質問

「Either の合成」レッスンは無料ですか?

はい。「Either の合成」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Scala for Backend Engineering & Functional Programmingコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Scala for Backend Engineering & Functional Programmingコースには全4レッスンが含まれています。

「Either の合成」で何を学びますか?

map と flatMap です。 ブラウザで直接実行するハンズオンコードでScala for Backend Engineering & Functional Programmingを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Scala for Backend Engineering & Functional Programmingを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのScala for Backend Engineering & Functional Programmingは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。

「Either の合成」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このScala for Backend Engineering & Functional Programmingレッスンでコードを書いて実行できますか?

はい。すべてのScala for Backend Engineering & Functional Programmingレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

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

  1. エラーに対する Either
  2. Try、Success、Failure
  3. Either の合成
  4. 型の相互変換
← Scala for Backend Engineering & Functional Programmingに戻る