みやしーです。
弊社FLINTERSが2024年1月6日に会社設立10周年ということで、みんなでブログを毎日書いています。この記事は113日目の記事だそうです。すごいですね
さっそく本題へ
対象読者
- Scalaをやっている
- 失敗と成功のいずれかを表すには Either[E, A] を使うことを知っている
- 非同期処理を表すには Future[A] を使うことを知っている
これまでのあらすじ
- Either と Future は同時に使うことが多いじゃん
- Either と Future は同時に使う… EitherT の出番か!?
- いや、EitherTなんかちょっとまどろっこしいな
わかるなあ
いやー EitherT 便利だと思ってたんですけどね。4年前の私は EitherT大好きって感じの記事を書いてキャッキャしていたし。
でも、非同期処理と失敗と成功を同時に表現できるすごい型があればそれで良くない?って思うわけですよ。
そうです
時代はZIO
ZIO !!!
ZIOっていうのは、大体 Future[Either[E, A]]
のことです!
便利ですね。現場からは以上です。
続き
ちょっと嘘つきました。
ドキュメントには 「(この例えは正確とは言えないが)ZIOは R => Either[E, A]
だ」と書いてますね。
Future[Either[E, A]]
とは色々違いますが、まあでも大体こんなもんです。
続き2
ZIO型には3つの型パラメータがあり、 ZIO[R, E, A]
のようになっています。
このうち E
と A
は Either[E, A]
と同様に失敗と成功を表すものです。
なお、 R
は関数の引数として使うようなものですが、使わないなら Any
を入れておけばOKです。
Either を使ったコードは、ZIOでは次のように書きます。
val a: Either[String, Int] = Right(42) val b: Either[String, Int] = Left("failed!")
import zio.ZIO val a: ZIO[Any, String, Int] = ZIO.succeed(42) val b: ZIO[Any, String, Int] = ZIO.fail("failed!")
.map
, .flatMap
のようなおなじみの処理や、 .mapError
によるエラーの値の変換ができます。
その他もろもろ、Either で出来ることは ZIO でもできます。
また、 Future に関してはというと、 Future と ZIO で非同期に関する考え方が違う点がいくつかあります。
Future ではこのようなコードがあったとき
import scala.concurrent.Future import scala.concurrent.ExecutionContext.Implicits.global val a: Furure[Int] = Future { // なんか色々通信したりする処理 42 }
- 型は、同期的な処理は
A
で、非同期的な処理はFuture[A]
と区別する - なんか
ExecutionContext
とかようわからんものを指定する必要がある - Future型のインスタンスが作成されたタイミングで非同期処理が開始される
ですよね。
ZIO の場合は、なんかこんな感じのコードになるのですが
val a: ZIO[Any, Throwable, Int] = ZIO.attemptBlocking { // なんか色々通信したりする処理 42 }
- 型は、同期的だろうと非同期だろうと
ZIO[R, E, A]
である .attemptBlocking
や.blocking
などのメソッドを使うことで、いい感じに実行してくれる- ZIO型のインスタンスが作成されたタイミングではまだ処理は実行されない
といった違いがあります。
まとめ
まあ、そんなこんなで Future[Either[E, A]]
と ZIO にはいくつかの違いはあるのですが、 ZIO っていう型一つで両方のことができます。
Future[Either[E, A]]
を用意して EitherT
でラップして〜みたいなこと、もうやらんでいいです。
Either も Future もアプリケーション書いてたら絶対使うわけでして、 ということはみんな ZIO を使ったらいいよってことですよね。
めでたしめでたし。