この記事は ウェブクルー Advent Calendar 2023 10日目の記事です。
昨日は@wc-ymmtさんの「p5.jsを試してみた」でした。
今年一年もScalaのZIOに携わる時間が比較的長かったので、ZIOの再起処理について新しく知ったことを書こうと思います。
本記事のコードに関しては以下のリポジトリのものになります。
スタックセーフについて
再起関数において以下の様に特にTrampolineや@tailrec
などを使わず、コールスタック量を抑えない状態で、深い再起処理となるように引数を設定し、実行すると、StackOverflowError
が発生します。
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
が発生します。
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
を抑制できます。
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のふわっとしている部分を再確認」です。