ライブラリを作成していていい感じに副作用なく非同期でIOを書きたくなったので改めてCatsEffectに入門したがドキュメントだけ見てもよくわからなかったので学習ついでにまとめることにした。
なので少し駆け足でゆるく描きます。
対象の読者
- CatsEffect3のドキュメントが難しいよぉという方。
MonadCancelとは
CatsのMonadErrorを継承したCatsEffect3で提供されるUnqueとClockを除く全ての型クラスの親となる型クラス。
機能としては名前の通り処理のキャンセルについての機能を提供する。
MonadErrorとの違い
一言で言うと回復可能かどうか。
/** MonadErrorはエラーから回復することができる。 */
val error = MonadError[IO,Throwable].raiseError[Int](new Throwable)
error.handleError(_ => 0).map(println)
// 0
/** handleErrorを使用してもキャンセルの状態が帰ってくる */
val canceled = MonadCancel[IO].canceled >> Applicative[IO].pure(1)
canceled.handleError(_ => 0).map(println)
// (出力されない)
/** 回復して値を返すのではなくキャンセル時のUnitを指定する */
canceled.onCancel(x: IO[Unit])
MonadCancel[F[_]].cancelはOption.Noneのような状態で、後続の処理は実行されない。
いくつか例:
IO.canceled >> IO.println("a") //
IO.println("b") >> IO.canceled // b
val c = IO.canceled >> IO.pure(10)
c.map(println) //
c.map(println).onCancel(IO.println("canceled!")) // canceled!
uncancelable
では特定の箇所のみキャンセルを許容したい場合はどうするか。というときにuncancelableを使う。
MonadCancel[F[_]].uncancelable { poll =>
???
}
このpoll内の境界でのみキャンセルを許容する事が出来る。
例えば
IO.uncancelable { poll =>
for {
_ <- IO.canceled
_ <- IO.println(1)
_ <- poll(IO.canceled).onCancel(IO.println("canceled!"))
_ <- IO.println(2)
} yield ()
}
// 1
// canceled!
となる。
気を付けるべきポイントとしてはonCancelの定義場所なのだが、
IO.canceled >> IO.println("hello").onCancel(IO.println("canceled!"))
//
と書いても何も表示されない。
というのもCanceledの後続は評価されないのが原因なので、上記の IO.println("hello") に対してキャンセル時の処理を定義したい場合は
(IO.canceled >> IO.println("hello")).onCancel(IO.println("canceled!"))
// canceled!
と書く必要がある。
guarantee
日本語で言うと保証。
onCancel が try-catch文で言うところのcatchにあたるのならば、guaranteeはfinallyにあたる。
キャンセルされたかどうかに関係なく処理を実行する。
IO.canceled.guarantee(IO.println("canceled!")).map(println)
// canceled!
IO.pure(1).guarantee(IO.println("unit!")).map(println)
// unit!
// 1
怖いのは処理される順番。guaranteeで定義された処理が先に実行されるので要注意。
まとめ
CatsEffectではCatsの知識も必要になるのでお勉強が大変。
それとソースにもコメントで説明が書いてあるのでわからない時はソース見るのもいいカモ。
以上、ありがとうございました。