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

トランポリン

スタックセーフな再帰です。

「トランポリン」は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レッスンが含まれています。

@tailrecの限界

@tailrecが最適化できるのは、関数が直接自分自身を呼び出す場合だけです。相互再帰(2つの関数が互いに呼び出し合う場合)には使えず、スタックは増加し続けます。これを解決するのがトランポリンです。

相互再帰の問題

isEvenとisOddが互いを使って定義されている場合を考えます。大きな数ではスタックオーバーフローが発生し、どちらにも@tailrecを付けられません。

object Main {
  def isEven(n: Int): Boolean = if (n == 0) true else isOdd(n - 1)
  def isOdd(n: Int): Boolean  = if (n == 0) false else isEven(n - 1)

  def main(args: Array[String]): Unit = {
    println(isEven(10))
  }
}

トランポリンとは

トランポリンは、再帰呼び出しをデータに変換します。自分自身を呼び出す代わりに、次のステップの説明を返します。ドライバーループがこれらのステップを繰り返し実行することで、スタックを一定に保ちます。

標準ライブラリのTailRec

Scalaはscala.util.control.TailCallsとTailRec型を提供しています。最終結果にはdone(x)を使い、次の呼び出しを遅延させるにはtailcall(...)を使います。

import scala.util.control.TailCalls._

object Main {
  def isEven(n: Int): TailRec[Boolean] =
    if (n == 0) done(true) else tailcall(isOdd(n - 1))
  def isOdd(n: Int): TailRec[Boolean] =
    if (n == 0) done(false) else tailcall(isEven(n - 1))

  def main(args: Array[String]): Unit = {
    println(isEven(100000).result)
  }
}

doneとtailcall

2つの基本要素は次のとおりです。

  • done(value)は最終的な答えをラップします。
  • tailcall(expr)は、TailRecを返す呼び出しを遅延させます。

.resultを呼び出すと、トランポリンループが実行され、値が生成されます。

スタックセーフ性

各tailcallはJavaの呼び出しをネストする代わりにドライバーループへ制御を戻すため、再帰の深さに応じてJVMスタックが増えることはありません。上の例では、オーバーフローを起こさずに100,000ステップを処理できます。

自己再帰をトランポリン化する

アキュムレーターを簡単には使えない通常の深い自己再帰にも、トランポリンを使用できます。ここでは、深いカウントダウンをスタックセーフに保ちます。

import scala.util.control.TailCalls._

object Main {
  def countDown(n: Int): TailRec[Int] =
    if (n == 0) done(0) else tailcall(countDown(n - 1))

  def main(args: Array[String]): Unit = {
    println(countDown(500000).result)
  }
}

flatMapで結果を組み合わせる

TailRecはmapとflatMapをサポートしているため、遅延させた呼び出しの後に処理を行いながら、スタックセーフ性を維持できます。

import scala.util.control.TailCalls._

object Main {
  def sum(n: Int): TailRec[Int] =
    if (n == 0) done(0)
    else tailcall(sum(n - 1)).map(_ + n)

  def main(args: Array[String]): Unit = {
    println(sum(100000).result)
  }
}

ドライバーループの仕組み

概念的には、.resultはループを実行します。現在のステップを取り出し、それがdoneなら値を返し、遅延された呼び出しなら1層分を評価して続行します。これらはすべて、一定のスタック領域で行われます。

Effectライブラリでのトランポリン化

Cats EffectやZIOなどのライブラリは、内部でflatMapチェーンをトランポリン化しています。そのため、スタックオーバーフローを起こさずに深くネストされたeffectプログラムを構築できます。トランポリン化は、スタックセーフな関数型effectの基盤です。

トランポリンを使う場面

トランポリンを使うのは次の場合です。

  • 単一の末尾再帰関数にはできない相互再帰がある場合。
  • 再帰が深すぎてスタックに収まらず、アキュムレーターも適さない場合。

単純な自己再帰では、まずアキュムレーター付きの@tailrecを優先してください。

クイックチェック

トランポリンについての理解を確認しましょう。

まとめ

トランポニーングについて学びました。

  • 相互再帰や非常に深い再帰を、スタックセーフにできます。
  • TailCallsでdone(x)とtailcall(...)を使い、その後.resultを使用します。
  • TailRecはmap/flatMapをサポートします。
  • スタックセーフなエフェクトライブラリの基盤となっています。

よくある質問

「トランポリン」レッスンは無料ですか?

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

「トランポリン」で何を学びますか?

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

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

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

「トランポリン」レッスンにはどのくらい時間がかかりますか?

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

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

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

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

  1. 再帰の基礎
  2. tailrec アノテーション
  3. アキュムレーターパターン
  4. トランポリン
← Scala for Backend Engineering & Functional Programmingに戻る