LoginSignup
8
0

ZIOのスタックセーフの注意点

Last updated at Posted at 2023-12-09

この記事は ウェブクルー Advent Calendar 2023 10日目の記事です。
昨日は@wc-ymmtさんの「p5.jsを試してみた」でした。

今年一年もScalaのZIOに携わる時間が比較的長かったので、ZIOの再起処理について新しく知ったことを書こうと思います。

本記事のコードに関しては以下のリポジトリのものになります。

スタックセーフについて

再起関数において以下の様に特にTrampoline@tailrecなどを使わず、コールスタック量を抑えない状態で、深い再起処理となるように引数を設定し、実行すると、StackOverflowErrorが発生します。

StackOverFlowExample.scala
def getFibonacciNaive(index: BigInt): BigInt =
  if (index <= 0)
    0
  else if (index == 1)
    1
  else
    getFibonacciNaive(index - 1) + getFibonacciNaive(index - 2)

StackOverflowErrorはアプリケーションの実行を止めてしまう可能性があるエラーとなり、このエラーの発生は極力抑えなければなりません。Trampoline@tailrecを使い、コールスタック量を抑え、再起処理の深さによらずStackOverflowErrorが発生しなくなった状態をスタックセーフと言います。
スタックセーフの定義は以下です。

再起処理の深さによらず、StackOverflowErrorが発生しなくなった状態

Stack safety is present where a function cannot crash due to overflowing the limit of number of recursive calls.

以下の様にZIOのドキュメント内にこのスタックセーフという言葉があるため、ZIOを使った処理内においてスタックセーフであり、再起処理においてStackOverflowErrorの発生を心配する必要はないものと考えていました。

Also like ZIO, ZPure is stack safe, so we can write recursive programs in terms of ZPure without worrying about stack overflow errors.

These defaults help to guarantee stack safety and cooperative multitasking. They can be changed in Runtime if automatic thread shifting is not desired.

ZIOでStackOverflowErrorが発生する例

ZIOのDiscordチャンネルにあった一例ですが、以下の階乗を計算する再起処理を実行するとStackOverflowErrorが発生します。

SoeRecursionZIO.scala
  private def factorial(n: Int): ZIO[Any, Throwable, BigInt] =
    if (n == 0)
      ZIO.succeed(1)
    else
      for {
        n2 <- factorial(n - 1)
      } yield n2 * n

StackOverflowErrorが起こり得る点については気を付けた方が良いと思いました。

StackOverflowErrorを発生させないための工夫

上記に以下の様にZIO.suspendを付けるとStackOverflowErrorを抑制できます。

RecursionZIO.scala
      for {
        n2 <- ZIO.suspend(factorial(n - 1))
      } yield n2 * n

Discordチャンネルへの投稿者の方も詳しい理由などはわからなかったとのことですが、以下のIssueを参考として取り上げていました。

以下の引用文から、実際の計算の際ではなく、ZIOのエフェクトの組み立ての際にStackOverflowErrorが発生するのかと考えました。そのため、そのような組み立てを遅延させるようなZIO.suspendを使うとStackOverflowErrorが発生しなくなるのかと考えたのですが、詳細をご存じの方がいましたらコメント頂けると幸いです。

My intuition for why the SOE happens is: the SOE happens during the construction of the monadic computation, not during evaluation. To prevent that, the construction must be suspended.

まとめ

ZIOを使った再起処理の場合でもStackOverflowErrorが発生する可能性は考慮する必要があるかと思いました。

明日は@wc-takaharaさんの「TypeScriptのふわっとしている部分を再確認」です。

8
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
0