Closed10

ZIO 使ってみる

yukiyuki

先日 ZIO に関する発表を同僚が社内カンファレンスでしていておもしろそうだったので、ちょっと触ってみる。適当なWebアプリケーションを作るところを目標にする。

yukiyuki

やる前のイメージとしては、cats の IO や Future では、エラー型は Nothing として潰されてしまうので、どのエラーが来ているかを型情報で伝えるのが結構難しい。まずその問題を解決しているもの、という勝手なイメージ。加えて、環境情報 R を突っ込めて、R の型レベルでの整合性の検証ができそうな感じもしている。

yukiyuki

Scala 3 でどこまでいけるのか試したい!

  • ZIO: 大丈夫そう
  • http4s: ???
  • doobie: ???
  • IntelliJ: △ (nightly build を求められる)
yukiyuki

簡単な Hello, world

package com.github.yuk1ty
import zio.*
import zio.console.{Console, putStrLn}

import java.io.IOException

object Main extends zio.App {
  override def run(args: List[String]) = program.exitCode

  val program: ZIO[Console, IOException, Unit] = for {
    value <- ZIO.succeed(42)
    _     <- putStrLn(s"value: $value")
  } yield ()
}

ただし、IDEA の指示通りに型付けした下記はコンパイルエラーになる。

package com.github.yuk1ty
import zio.*
import zio.console.{Console, putStrLn}

import java.io.IOException

object Main extends zio.App {
  override def run(args: List[String]) = program.exitCode

  val program: ZIO[Console, IOException, Unit] = for {
    value <- ZIO.succeed(42)
    _     <- putStrLn(s"value: $value")
  } yield ()
}
Found:    zio.URIO[zio.console.Console, zio.ExitCode]
Required: zio.URIO[Any, zio.ExitCode]
  override def run(args: List[String]): URIO[Any, ExitCode] = program.exitCode

Any にしているのがまずいだけの可能性があるので、下記のように型合わせをしてみた。すると通った。実運用上はこれで正しいんだろうか。

package com.github.yuk1ty
import zio.*
import zio.console.{Console, putStrLn}

import java.io.IOException

object Main extends zio.App {
  override def run(args: List[String]): URIO[Console, ExitCode] = program.exitCode

  val program: ZIO[Console, IOException, Unit] = for {
    value <- ZIO.succeed(42)
    _     <- putStrLn(s"value: $value")
  } yield ()
}
yukiyuki

DI の例はあるんだけど、複数個サービスを注入したいみたいな例のときにどうすればいいかが書いてなかったので試した。ZIO.environment で環境をセットアップすることができる。

https://zio.dev/version-1.x/overview/overview_testing_effects

Scala 3 とかだと Union Type があるのでそれが適用されているんだと思われる。for-yield 内で適用に結合しても、きちんとその分正しく結合してくれるようみたい。なかなか便利そうな気がいしている。

// 中略
  class ServiceA
  class ServiceB

  val dicheck: ZIO[ServiceB with ServiceA, Nothing, Unit] = for {
    serviceA <- ZIO.environment[ServiceA]
    serviceB <- ZIO.environment[ServiceB]
  } yield ()

DI しつつ、そのモジュールのメソッドを使用したい場合は accessM ないしは access という関数を使用できる。

// 中略
  class ServiceA {
    def print(): Task[Unit] = ZIO.effect(println("serviceA"))
  }
  class ServiceB {
    def print(): Task[Unit] = ZIO.effect(println("serviceB"))
  }

  val dicheck: ZIO[ServiceB with ServiceA, Throwable, Unit] = for {
    _ <- ZIO.accessM[ServiceA](_.print())
    _ <- ZIO.accessM[ServiceB](_.print())
  } yield ()
yukiyuki

レイヤードアーキテクチャのようにレイヤーになるアプリケーションでどうやったら ZIO の環境サイドを上手に扱えるかを考えている。

https://github.com/adrianfilip/zio-crud-sample

これはいい例だけど、一層しかないので複数になった場合にどうなるのかは考える必要がある。

このスクラップは2021/12/21にクローズされました