Monix v3.0.0-RC2からTask.applyの挙動が変わっている

最近、Monix というScalaの非同期プログラミング用ライブラリを使い始めました。

monix.io

最初は、最新安定版である v2.3.3 で色々触ってみて「これは使える!楽しい!」となって、v3.x 系の方はどんな感じかなと思いバージョンを上げてみると、メソッド名をはじめ、色々変わりまくっていることに焦りました。その中で Task.apply の挙動の変更には注意が必要だなと思ったので書いておきます。

v3.x系でのTask.applyの変更

v2.x 系では、以下のコードは非同期処理になっていたと思います。

val task = Task { // Task.apply
  1 + 2
}

ところが、v3.x 系ではこの挙動が同期処理に変わるようです。つまり、Task.applyTask.eval は同じ挙動になる。

Task.applyのコードを比較してみる

それでは、v2.x 系と v3.x 系の Task のコードを比較してみましょう。以下は抜粋です。

Monix v2.3.3

object Task extends TaskInstances {
  /** Returns a new task that, when executed, will emit the result of
    * the given function, executed asynchronously.
    *
    * @param f is the callback to execute asynchronously
    */
  def apply[A](f: => A): Task[A] =
    fork(eval(f))

https://github.com/monix/monix/blob/v2.3.3/monix-eval/shared/src/main/scala/monix/eval/Task.scala#L662-L669

Monix v3.0.0-RC2

object Task extends TaskInstancesLevel1 {
  /** Lifts the given thunk in the `Task` context, processing it synchronously
    * when the task gets evaluated.
    *
    * This is an alias for:
    *
    * {{{
    *   val thunk = () => 42
    *   Task.eval(thunk())
    * }}}
    *
    * WARN: behavior of `Task.apply` has changed since 3.0.0-RC2.
    * Before the change (during Monix 2.x series), this operation was forcing
    * a fork, being equivalent to the new [[Task.evalAsync]].
    *
    * Switch to [[Task.evalAsync]] if you wish the old behavior, or combine
    * [[Task.eval]] with [[Task.executeAsync]].
    */
  def apply[A](@deprecatedName('f) a: => A): Task[A] =
    eval(a)

https://github.com/monix/monix/blob/v3.0.0-RC2/monix-eval/shared/src/main/scala/monix/eval/Task.scala#L2376-L2395

v3.x 系のコメントにこのような記述があります。

WARN: behavior of Task.apply has changed since 3.0.0-RC2. Before the change (during Monix 2.x series), this operation was forcing a fork, being equivalent to the new Task.evalAsync.

以下は機械翻訳

警告:Task.applyの動作が3.0.0-RC2以降に変更されました。変更前(Monix 2.xシリーズ中)、この操作は新しいTask.evalAsyncと同等のフォークを強制していました。

つまり、v3.x 系で非同期な Task を使いたかったら Task.apply ではなく Task.evalAsync を使ってねということみたいです。

終わりに

今回の変更を受けて、 今後は Task.apply ではなく、Task.evalAsync で非同期なのか Task.eval で同期なのかを明示的に書いた方が良いのかなと思いました。v3.x 系では他にも Callback が任意のエラー型を設定できるようになってたりと大きな変更があるみたいですね。