ZIOのエラー処理

# ZIOの3種類のエラー
ZIOのエラーは全体で以下の3種類に大別されます。

https://zio.dev/reference/error-management/types/

## Failures
ZIOにおける最も代表的なエラーであると言えるかもしれません。このエラーは一般的に予測されるエラーを表しています。
予測可能なエラーとなるため、このエラーについては適切なハンドリング処理を行うことが期待されています。
そのため、``ZIO[R,E,A]``型の型パラメータの第二引数に現れるという特徴があります。``ZIO[R,E,A]``という場合は``E``になります。
具体的には以下の様な場合に発生するエラーです。

1. ``ZIO.attempt``内の処理でのエラー

## Defects
このエラーは一般的に予測できないエラーとなります。
そのため、このエラーの型についても同様に予測できず、``ZIO``型の型パラメータ上にも現れません。
具体的には以下の様な場合に発生するエラーです。

1. ``ZIO.succeed``内の処理でのエラー
1. ZIO型を返す関数の引数における処理でのエラー

## Fatal
このエラーは予測できない``StackOverflowError``といったJVM関係のエラーとなります。
このエラーが発生した場合、ZIOアプリケーションは強制終了するため、ハンドリング処理は行えず、出来る処理としてはエラーに関するログ出力処理のみになります。

1. ZIO型を返さず、末尾再帰による最適化などがなされていない通常の再起処理における``StackOverflowError``

# それぞれの具体例
これからこれら3つのエラーについて、実際にエラーを意図的に発生させ、具体的に見ていきます。

## Failures
このfailureエラーについては意図的に発生させる方法はいくつかあるのですが、今回は``ZIO.fail``関数を使った方法により発生させ、どのような挙動を示すかについて確認します。
``Hello, World!``プログラムについて以下の様に改変します。

```scala:ApplicationServiceImpl.scala
case class ApplicationServiceImpl(currentDate: Date) extends ApplicationService {
  override def consoleOutput(): ZIO[Any, Throwable, Unit] =
    for{
      _ <- ZIO.fail(new Exception("This is a failure test."))
      _ <- Console.printLine(
      s"${new SimpleDateFormat ("yyyy/MM/dd HH:mm:ss").format (currentDate)} Hello, World!"
      )
    } yield ()
}
```
```scala:main.scala
object MainApp extends ZIOAppDefault {
  def run: ZIO[Any, Throwable, Unit] = ApplicationService.consoleOutput().provide(
    ZLayer.fromZIO(ZIO.attempt {
      import java.text.SimpleDateFormat
      // 任意の日付文字列
      val inpDateStr = "2023/07/25 17:46:00"

      // 取り扱う日付の形にフォーマット設定
      val sdformat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss")

      // Date型に変換( DateFromatクラスのparse() )
      sdformat.parse(inpDateStr)
    }),
    ApplicationServiceImpl.layer
  ).catchAll(failure => Console.printError("Failure:" + failure.toString))
}
```
こちらについて実行すると以下の様に出力されます。
```shell
Failure:java.lang.Exception: This is a failure test.
```
``2023/07/25 Hello, World!``が出力されなかったのはこれを出力する処理の前にfailureエラーが発生したためとなります。
発生したfailureエラーは上層に伝播していき、``catchAll``関数で最終的に処理されました。
その結果、このような出力が行われました。
``catchAll``関数はfailureエラーを処理する関数になります。
このようにfailureエラーの型については``ZIO[R,E,A]``型の型パラメータの第二引数と一致する必要があります。
今回の場合ですと第二引数が``Throwable``であるため、そのサブクラスである``Exception``型とすることができます。
## Defects
次にdefectエラーを見ます。
今回は``ZIO.die``関数を使った方法により発生させます。
先ほどのプログラムを以下のように改変します。
```scala:ApplicationServiceImpl.scala
case class ApplicationServiceImpl(currentDate: Date) extends ApplicationService {
  override def consoleOutput(): ZIO[Any, IOException, Unit] =
    for{
      _ <- ZIO.die(new ArithmeticException("divide by zero"))
      _ <- Console.printLine(
      s"${new SimpleDateFormat ("yyyy/MM/dd HH:mm:ss").format (currentDate)} Hello, World!"
      )
    } yield ()
}
```
```scala:ApplicationService.scala
package application.service

import zio.ZIO

import java.io.IOException

trait ApplicationService {
  def consoleOutput(): ZIO[Any, IOException, Unit]
}

object ApplicationService {
  def consoleOutput(): ZIO[ApplicationService, IOException, Unit] =
    ZIO.serviceWithZIO[ApplicationService](_.consoleOutput())
}
```
こちらについて実行すると以下の様に出力されます。
```shell
timestamp=2023-07-28T06:50:49.586017200Z level=ERROR thread=#zio-fiber-1 message="" cause="Exception in thread "zio-fiber-4" java.lang.ArithmeticException: divide by zero
        at application.service.impl.ApplicationServiceImpl.consoleOutput$$anonfun$1(ApplicationServiceImpl.scala:13)
        at zio.ZIO$.die$$anonfun$1(ZIO.scala:3081)
        at zio.ZIO$.failCause$$anonfun$1$$anonfun$1$$anonfun$1(ZIO.scala:3155)
        at application.service.impl.ApplicationServiceImpl.consoleOutput(ApplicationServiceImpl.scala:13)
        at application.service.impl.ApplicationServiceImpl.consoleOutput(ApplicationServiceImpl.scala:17)
        at <empty>.MainApp.run(main.scala:6)
        at <empty>.MainApp.run(main.scala:19)"
```
``Failure:``という文字列は表示されず、``catchAll``関数でこのエラーが処理されなかったことがわかります。
このことからこのdefectエラーは先ほどのfailureエラーとは別の種類のエラーであることがわかります。
また、今回``ZIO``型の型パラメータの第二引数を``Throwable``から``IOException``に変更しておりますが、``ZIO.die``で発生させているエラーは``ArithmeticException``となっています。ところがコンパイルエラーとはならず実行自体はできています。
これは先述の通りdefectエラーは一般的に予測できない類のエラーを表しているため、型パラメータの第二引数とは直接的に関係しないためとなります。
試しに``_ <- ZIO.die(new ArithmeticException("divide by zero"))``の部分を``_ <- ZIO.fail(new ArithmeticException("divide by zero"))``と変更するとコンパイルエラーとなることがわかります。
<br>
``catchAll``関数はfailureエラーを処理するための関数でしたが、defectエラーを処理するための関数として``catchAllDefect``関数があります。
以下のようにプログラムを変更してください。
```scala:main.scala
  ).catchAll(failure => Console.printError("Failure:" + failure.toString)).catchAllDefect(defect =>
    Console.printError("Defect:" + defect.toString)
  )
```
これを実行すると以下の様に出力されます。
```shell
Defect:java.lang.ArithmeticException: divide by zero
```
``catchAllDefect``関数によりdefectエラーを処理することが出来ました。
## Fatal
最後にFatalエラーです。
先ほどのプログラムについて以下の様に変更します。
```scala:ApplicationServiceImpl.scala
case class ApplicationServiceImpl(currentDate: Date) extends ApplicationService {
  override def consoleOutput(): ZIO[Any, Throwable, Unit] =
    for {
      _ <- ZIO
        .attempt(
          throw new StackOverflowError(
            "The call stack pointer exceeds the stack bound."
          )
        )
      _ <- Console.printLine(
        s"${new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(currentDate)} Hello, World!"
      )
    } yield ()
}
```
```scala:ApplicationService.scala
trait ApplicationService {
  def consoleOutput(): ZIO[Any, Throwable, Unit]
}

object ApplicationService {
  def consoleOutput(): ZIO[ApplicationService, Throwable, Unit] =
    ZIO.serviceWithZIO[ApplicationService](_.consoleOutput())
}
```
``StackOverflowError``を意図的に発生させるように修正しました。また、型パラメータの第二引数の型についても``Throwable``型に戻しています。
これを実行すると以下の様に出力されます。
```shell
**** WARNING ****
Catastrophic error encountered. Application not safely interrupted. Resources may be leaked. Check the logs for more details and consider overriding `Runtime.reportFatal` to capture context.
java.lang.StackOverflowError: The call stack pointer exceeds the stack bound.
(以下省略)
```
出力された文字列に``Failure:``も``Defect:``も含まれないことからfailureエラーを処理する``catchAll``関数でもdefectエラーを処理する``catchAllDefect``関数でも処理できていないことがわかり、これらとは別種のエラーであることが確かめられました。

# 終わりに
今回はZIOのエラー処理について見てきました。
前章の[ZIOのDIについて](https://qiita.com/wc_sangen/private/38672e5fe7d651ff1533)と併せ、``ZIO[R, E, A] ``型の意味合いについての基本的な理解が得られたかと思います。

次章では``ZIO Quill``を例としてZIOエコシステムのライブラリの利用、DB接続について見ていきます。

前章:[ZIOのDIについて](https://qiita.com/wc_sangen/private/38672e5fe7d651ff1533)
次章:[DB接続(ZIO Quill)](https://qiita.com/wc_sangen/private/37436c567f4015a02ba7)

# 演習
1. 今回の3つのエラーについて実際に動かして挙動を確かめてください。
    1. ``Failures``の解答例は以下になります。
    https://github.com/hatuda/zio-practice/tree/CHAPTER3-Failures
    1. ``Defects``の解答例は以下になります。
    https://github.com/hatuda/zio-practice/tree/CHAPTER3-Defects
    1. ``Fatal``の解答例は以下になります。
    https://github.com/hatuda/zio-practice/tree/CHAPTER3-Fatal
1. 今回の方法とは別の方法でそれぞれのエラーを発生させ、挙動を確かめてください。
    1. ``Failures``の解答例は以下になります。
    https://github.com/hatuda/zio-practice/tree/CHAPTER3-Failures-Another
    1. ``Defects``の解答例は以下になります。
    https://github.com/hatuda/zio-practice/tree/CHAPTER3-Defects-Another
    1. ``Fatal``の解答例は以下になります。
    https://github.com/hatuda/zio-practice/tree/CHAPTER3-Fatal-Another