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

Option と Future の利用

モナドを連鎖させます。

レッスン 4/413 ステップ

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

コレクションを超えて

for内包表記はリストに限定されません。mapとflatMapを提供する型であれば使用でき、Option、Either、Try、Futureなどが該当します。これはモナド的な連鎖と呼ばれます。

object Main {
  def main(args: Array[String]): Unit = {
    val sum = for {
      a <- Some(10)
      b <- Some(20)
    } yield a + b
    println(sum)
  }
}

OptionはNoneで短絡する

Optionでは、すべてのジェネレーターがSomeの場合にのみ、内包表記から結果が生成されます。いずれかがNoneになると、全体の結果がNoneになり、後続のステップはスキップされます。

object Main {
  def main(args: Array[String]): Unit = {
    val ok = for {
      a <- Some(2)
      b <- Some(3)
    } yield a * b

    val missing = for {
      a <- Some(2)
      b <- None: Option[Int]
    } yield a * b

    println(ok)
    println(missing)
  }
}

オプショナルな検索を連鎖させる

よくあるパターンは、各検索がOptionを返すMapでキーを検索することです。for内包表記からは、すべての検索が成功した場合にのみ値が生成されます。

object Main {
  def main(args: Array[String]): Unit = {
    val prices = Map("apple" -> 3, "bread" -> 5)
    val total = for {
      a <- prices.get("apple")
      b <- prices.get("bread")
    } yield a + b
    println(total)

    val missing = for {
      a <- prices.get("apple")
      c <- prices.get("milk")
    } yield a + c
    println(missing)
  }
}

ネストしたif-elseより優れている理由

for内包表記を使わない場合は、パターンマッチやnullチェックをネストする必要があります。モナド形式なら、そのピラミッド状の構造をすっきりした直線的な流れにできます。

object Main {
  def parse(s: String): Option[Int] = s.toIntOption
  def main(args: Array[String]): Unit = {
    val result = for {
      x <- parse("40")
      y <- parse("2")
    } yield x + y
    println(result)
  }
}

ガードはOptionでも使える

Optionの内包表記内にガードを置くと、条件を満たさない場合に値がNoneになり、インラインの検証として機能します。

object Main {
  def main(args: Array[String]): Unit = {
    def validate(age: Int): Option[Int] =
      for {
        a <- Some(age)
        if a >= 18
      } yield a
    println(validate(25))
    println(validate(15))
  }
}

Futureの概要

Futureは、後で利用可能になる値を表します。Optionと同様にmapとflatMapを持つため、for内包表記で非同期処理のステップを順番に実行できます。

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Await
import scala.concurrent.duration._

object Main {
  def main(args: Array[String]): Unit = {
    val f = for {
      a <- Future(10)
      b <- Future(20)
    } yield a + b
    println(Await.result(f, 2.seconds))
  }
}

ここではFutureが順番に実行される

for内包表記内で一方のジェネレーターが別のジェネレーターに依存している場合、各flatMapが前の結果を待つため、Futureは順番に実行されます。

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Await
import scala.concurrent.duration._

object Main {
  def main(args: Array[String]): Unit = {
    val f = for {
      a <- Future(5)
      b <- Future(a * 2)
    } yield a + b
    println(Await.result(f, 2.seconds))
  }
}

Futureを並列実行する

Futureを並列に実行するには、for内包表記の前に開始してから組み合わせます。独立した処理が待ち合わせずに重なって実行されます。

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Await
import scala.concurrent.duration._

object Main {
  def main(args: Array[String]): Unit = {
    val fa = Future(100)
    val fb = Future(200)
    val combined = for {
      a <- fa
      b <- fb
    } yield a + b
    println(Await.result(combined, 2.seconds))
  }
}

より詳細なエラーを扱うEither

Either[E, A]は、失敗時にエラー値を保持します。for内包表記では、最初のLeftで短絡し、そのLeftが返されます。

object Main {
  def parse(s: String): Either[String, Int] =
    s.toIntOption.toRight(s"not a number: $s")
  def main(args: Array[String]): Unit = {
    val good = for { a <- parse("3"); b <- parse("4") } yield a + b
    val bad  = for { a <- parse("3"); b <- parse("x") } yield a + b
    println(good)
    println(bad)
  }
}

同じ形を多くの型で使える

Option、Either、Try、Futureのどれを使っても、内包表記の見た目は同じであることに注目してください。異なるのは失敗時のセマンティクスだけです。この一貫性こそが、モナド的な連鎖の力です。

import scala.util.Try

object Main {
  def main(args: Array[String]): Unit = {
    val result = for {
      a <- Try("21".toInt)
      b <- Try("2".toInt)
    } yield a * b
    println(result)
  }
}

型を混在させることはできない

1つの内包表記に含まれるすべてのジェネレーターは、同じモナドでなければなりません。OptionのジェネレーターとFutureのジェネレーターを直接混在させることはできません。先にどちらかを変換してください(たとえばFuture.fromTryを使うか、Optionをラップします)。

object Main {
  def main(args: Array[String]): Unit = {
    // Convert the Option to keep types consistent
    val maybe: Option[Int] = Some(7)
    val result = for {
      a <- maybe
      b <- maybe.map(_ + 1)
    } yield a + b
    println(result)
  }
}

クイックチェック

Optionのfor内包表記で、いずれかのジェネレーターがNoneを生成するとどうなりますか?

まとめ

for内包表記によるモナド的な連鎖について学びました。

  • mapとflatMapを持つ任意の型で動作します
  • OptionはNoneで短絡します
  • EitherとTryは失敗時に短絡し、エラー情報を保持します
  • Futureは非同期処理のステップを順番に実行します。並列化するには早めに開始します
  • すべてのジェネレーターが同じモナド型を共有する必要があります
無料で開始

AI チューターと学ぶ Scala — 無料

ブラウザでリアルコードを書いて実行し、24/7 の AI チューターから瞬時にサポートを受け、ウェブまたはアプリで続きから学習できます。

コース
39
レッスン
143

よくある質問

「Option と Future の利用」レッスンは無料ですか?

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

「Option と Future の利用」で何を学びますか?

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

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

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

「Option と Future の利用」レッスンにはどのくらい時間がかかりますか?

ほとんどの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に戻る