Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Monix Taskが便利だという話

1,618 views

Published on

Yokohama.scalaと、2018年5月のセプテーニ・オリジナル社での勉強会資料です。

Published in: Software

Monix Taskが便利だという話

  1. 1. MONIX TASKが便利だという話 Taisuke Oe ( )@OE_uia
  2. 2. 今日話すこと Monix version 2.3.3 主にMonix Taskの話をします。 Monix Observable, Iterantは出てきません。
  3. 3. MONIXとは JVM Scala及びScala.js向け並行プログラミング用ライブラリ 当初はScala向けReactiveXの実装からスタートした 現在ではTypelevelプロジェクトとして、関数型プログラミング用ライブラリと深く統合している Monix2系まではcoreではなくCats及びScalaz向けモジュールをそれぞれ提供していた Monix3系からCatsに依存している Monix TaskはScalaz Taskにインスパイアされている
  4. 4. 単純なMONIX TASKの使い方 import monix.execution.Scheduler.Implicits.global import monix.eval.Task import scala.util.{Success, Failure} val sumTask = Task{println("side effect!");1+2} val cancelable = sumTask.runOnComplete { case Success(value) => println(s"result:$value") case Failure(ex) => println("Oh-no!") } //side effect! //result:3 //もし気が変わってキャンセルしたければ: //cancelable.cancel() cancelableなので: Task.chooseFirstOfList により、競争状態のTaskのうち最初に完了したもの以外をキャンセル timeout を設定可能
  5. 5. TASK.APPLYは非同期な遅延評価 Task#apply val sumTask = Task(1+2)
  6. 6. 参考:SCALA標準のFUTURE.APPLYの実装は先行評価 import scala.concurrent._ import ExecutionContext.Implicits.global Future{println("side effect!") ; 1+2} //side effect! Future.foreach(println) //3
  7. 7. RUNONCOMPLETE / RUNASYNC val cancelable = sumTask.runOnComplete { case Success(value) => println(s"result:$value") case Failure(ex) => println("Oh-no!") } //side effect! //result:3 遅延評価Taskを実行する 引数として、完了時のCallbackを登録できる 戻り値として、Cancelableを返す
  8. 8. TASKインスタンスの評価方法 以下の2軸について変わるので、マトリックスで整理しましょう 実行モデル: 同期 or 非同期 評価戦略: 先行評価 or 遅延評価
  9. 9. TASKインスタンス生成と評価方法の関係 非同期 同期 遅延評価 apply 先行評価
  10. 10. 基本は遅延評価。 val task = Task.eval{println("side effect!");1+2} task.runAsync.foreach(println) //side effect! //3 Task.evalのエイリアスとして、(scalaz Taskのメソッド名と同じ)Task.delayも用意されている。
  11. 11. 遅延評価した値をメモ化可能 val task = Task.evalOnce{println("side effect!");1+2} task.runAsync.foreach(println) //side effect! //3 task.runAsync.foreach(println) //3 Task.eval(...).memoizeと等価。
  12. 12. 成功値のみメモ化することも可 var effect = 0 val task = Task.eval{ effect += 1 if(effect < 2) throw new RuntimeException("boom!") effect }.memoizeOnSuccess val callback:Try[Int] => Unit = { case Success(value) => println(value) case Failure(ex) => println(ex) } task.runOnComplete(callback) //java.lang.RuntimeException: boom! task.runOnComplete(callback) //2 task.runOnComplete(callback) //2
  13. 13. TASKインスタンス生成と評価方法の関係 非同期 同期 遅延評価 apply eval, delay, evalOnce 先行評価
  14. 14. 評価済みの値をラップするため、先行評価もできる val task = Task.now{println("side effect!");1+2} //side effect! task.runAsync.foreach(println) //3 Scala標準のFuture.successful、scalaz TaskのTask.nowと同じ。
  15. 15. TASKインスタンス生成と評価方法の関係 非同期 同期 遅延評価 apply eval, delay, evalOnce 先行評価 now, pure
  16. 16. 評価戦略を先行評価から遅延評価へ変更 val task = Task.defer(Task.now{println("side effect!");1+2}) task.runAsync.foreach(println) //side effect! //3 Task.deferのエイリアスとして、(Scalaz Taskのメソッド名と同じ)Task.suspendが用意されて いる
  17. 17. TASKインスタンス生成と評価方法の関係 非同期 同期 遅延評価 apply eval, delay, evalOnce 先行評価 now, pure,failed 遅延 ↑ : Task.defer / Task.suspend 先行
  18. 18. SCALA標準FUTUREからTASKインスタンス生成可 import scala.concurrent.Future import monix.execution.Scheduler.Implicits.global val task = Task.fromFuture(Future{println("side effect!");1+2}) //side effect! task.runAsync.foreach(println) //3 Task.fromFutureだと、(Futureが先行評価なので)先行評価される非同期Taskインスタンス が生成。
  19. 19. SCALA標準FUTUREからTASKインスタンス生成可(遅延評価したい) import scala.concurrent.Future import monix.eval.Task val task = Task.deferFutureAction{ implicit scheduler => Future{println("side effect!");1+2} } import monix.execution.Scheduler.Implicits.global task.runAsync.foreach(println) //side effect! //3 Futureを生成する関数をTask.deferFutureActionでラップすると、 遅延評価される非同期 Taskインスタンスが生成可能。
  20. 20. TASKインスタンス生成と評価方法の関係 非同期 同期 遅延評価 apply, deferFutureAction eval, delay, evalOnce 先行評価 fromFuture now, pure,raiseError 遅延 ↑ : Task.defer / Task.suspend 先行
  21. 21. コールバック関数からTASKを生成 import monix.eval.{Task, Callback} import monix.execution.Scheduler import scala.concurrent.duration._ import scala.util.{Success,Failure} class HttpClient(handler:HttpHandler){ def get(url:String):Unit = handler.onSuccess(s"body mock from `$url`") } class HttpHandler(callback:Callback[String]){ def onSuccess(body:String):Unit = callback(Success(body)) def onFailure(ex:Throwable):Unit = callback(Failure(ex)) } val task = Task.create[String]{ (scheduler,callback) => scheduler.scheduleOnce(1 second){ new HttpClient(new HttpHandler(callback)).get("https://google.com/" } } task.runAsync.foreach(println)
  22. 22. Task.createのエイリアスとして、(scalaz Taskのメソッド名と同じ) Task.asyncも用意されてい る。
  23. 23. TASKインスタンス生成と評価方法の関係 非同期 同期 遅延評価 apply, create, async, evalOnce, deferFutureAction eval, delay, evalOnce 先行評価 fromFuture now, pure 遅延 ↑ : Task.defer / Task.suspend 先行
  24. 24. TASK#EXECUTEWITHFORKによる非同期化 val task = Task.eval{ println(s"side effect at:${Thread.currentThread.getName}") 1+2 } task.runAsync.foreach(println) //side effect at:run-main-0 //3 val asyncTask = task.executeWithFork asyncTask.runAsync.foreach(println) //side effect at:scala-execution-context-global-59 //3 executeOn で実行するSchedulerを明示的に指定できる Monix 3系では executeAsync にrenameされた
  25. 25. TASKインスタンス生成と評価方法の関係 非同期 同期 遅延評価 apply, create, async, evalOnce, deferFutureAction eval, delay, evalOnce 先行評価 fromFuture now, pure 遅延 ↑ : Task.defer / Task.suspend 先行 非同期 ← 同期 : executeWithFork / executeOn 非同期 → 同期 : coeval
  26. 26. 実行モデル
  27. 27. SCALA標準FUTUREの実行モデル object Future{ def apply[T](t: =>T)(implicit executor:ExecutionContext):Future[T] } trait Future[+T] extends Awaitable[T]{ def flatMap[S](f: T => Future[S])(implicit executor:ExecutionContext):Future } Future.apply, Future# atMap等の各関数が暗黙の引数としてとる ExecutionContext の実装によって決まる。 すなわち、 atMapによる合成ごとにContext Switchが発生する。
  28. 28. MONIX TASKの実行モデル Task.runAsync,runOnCompleteが暗黙の引数としてとるmonix.execution.Schedulerの実装によって決まる。 Task# atMapによる合成では Context Switchが起きない Task#executeOnやTask#asyncBoundaryにより非同期境界を指定し、runAsync するときに実行モデルを決定できる。
  29. 29. MONIX TASKにおけるCONTEXT SWITCH val start = Task(println(s"start at:${Thread.currentThread.getName}")) val imHere = Task.eval(println(s"I'm here:${Thread.currentThread.getName} val composed = for{ _ <- start _ <- imHere.executeOn(Scheduler.io(name = "io")) _ <- imHere.asyncBoundary _ <- imHere } yield () composed.runAsync(Callback.empty) /* * start at:scala-execution-context-global-205 * I'm here:io-206 * I'm here:io-206 * I'm here:scala-execution-context-global-205 */
  30. 30. MONIX TASKのまとめ デフォルトが遅延評価なので、immutableかつ参照透過で扱いやすい。 TaskのrunAsync/runOnCompleteでThreadPoolを渡す設計となっており、柔軟にContext Switchを制御可能。実効性能上有 利。 cancelableや、Scala標準Futureとのinteropなど、何かと痒いところに手が届く Monix3系からCatsに依存してしまっている。 失敗型が Throwable であるため、エラーを独自定義型の戻り値で表現したい場合使いにくい。

×