Monix Taskが便利だという話 | PPT
SlideShare a Scribd company logo
MONIX TASKが便利だという話
Taisuke Oe ( )@OE_uia
今日話すこと
Monix version 2.3.3
主にMonix Taskの話をします。
Monix Observable, Iterantは出てきません。
MONIXとは
JVM Scala及びScala.js向け並行プログラミング用ライブラリ
当初はScala向けReactiveXの実装からスタートした
現在ではTypelevelプロジェクトとして、関数型プログラミング用ライブラリと深く統合している
Monix2系まではcoreではなくCats及びScalaz向けモジュールをそれぞれ提供していた
Monix3系からCatsに依存している
Monix TaskはScalaz Taskにインスパイアされている
単純な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 を設定可能
TASK.APPLYは非同期な遅延評価
Task#apply
val sumTask = Task(1+2)
参考:SCALA標準のFUTURE.APPLYの実装は先行評価
import scala.concurrent._
import ExecutionContext.Implicits.global
Future{println("side effect!") ; 1+2}
//side effect!
Future.foreach(println)
//3
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を返す
TASKインスタンスの評価方法
以下の2軸について変わるので、マトリックスで整理しましょう
実行モデル: 同期 or 非同期
評価戦略: 先行評価 or 遅延評価
TASKインスタンス生成と評価方法の関係
非同期 同期
遅延評価 apply
先行評価
基本は遅延評価。
val task = Task.eval{println("side effect!");1+2}
task.runAsync.foreach(println)
//side effect!
//3
Task.evalのエイリアスとして、(scalaz Taskのメソッド名と同じ)Task.delayも用意されている。
遅延評価した値をメモ化可能
val task = Task.evalOnce{println("side effect!");1+2}
task.runAsync.foreach(println)
//side effect!
//3
task.runAsync.foreach(println)
//3
Task.eval(...).memoizeと等価。
成功値のみメモ化することも可
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
TASKインスタンス生成と評価方法の関係
非同期 同期
遅延評価 apply eval, delay, evalOnce
先行評価
評価済みの値をラップするため、先行評価もできる
val task = Task.now{println("side effect!");1+2}
//side effect!
task.runAsync.foreach(println)
//3
Scala標準のFuture.successful、scalaz TaskのTask.nowと同じ。
TASKインスタンス生成と評価方法の関係
非同期 同期
遅延評価 apply eval, delay, evalOnce
先行評価 now, pure
評価戦略を先行評価から遅延評価へ変更
val task = Task.defer(Task.now{println("side effect!");1+2})
task.runAsync.foreach(println)
//side effect!
//3
Task.deferのエイリアスとして、(Scalaz Taskのメソッド名と同じ)Task.suspendが用意されて
いる
TASKインスタンス生成と評価方法の関係
非同期 同期
遅延評価 apply eval, delay, evalOnce
先行評価 now, pure,failed
遅延
↑ : Task.defer / Task.suspend
先行
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インスタンス
が生成。
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インスタンスが生成可能。
TASKインスタンス生成と評価方法の関係
非同期 同期
遅延評価 apply, deferFutureAction eval, delay, evalOnce
先行評価 fromFuture now, pure,raiseError
遅延
↑ : Task.defer / Task.suspend
先行
コールバック関数から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)
Task.createのエイリアスとして、(scalaz Taskのメソッド名と同じ) Task.asyncも用意されてい
る。
TASKインスタンス生成と評価方法の関係
非同期 同期
遅延評価 apply, create, async, evalOnce, deferFutureAction eval, delay, evalOnce
先行評価 fromFuture now, pure
遅延
↑ : Task.defer / Task.suspend
先行
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された
TASKインスタンス生成と評価方法の関係
非同期 同期
遅延評価 apply, create, async, evalOnce, deferFutureAction eval, delay, evalOnce
先行評価 fromFuture now, pure
遅延
↑ : Task.defer / Task.suspend
先行
非同期 ← 同期 : executeWithFork / executeOn
非同期 → 同期 : coeval
実行モデル
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が発生する。
MONIX TASKの実行モデル
Task.runAsync,runOnCompleteが暗黙の引数としてとるmonix.execution.Schedulerの実装によって決まる。
Task# atMapによる合成では Context Switchが起きない
Task#executeOnやTask#asyncBoundaryにより非同期境界を指定し、runAsync するときに実行モデルを決定できる。
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
*/
MONIX TASKのまとめ
デフォルトが遅延評価なので、immutableかつ参照透過で扱いやすい。
TaskのrunAsync/runOnCompleteでThreadPoolを渡す設計となっており、柔軟にContext Switchを制御可能。実効性能上有
利。
cancelableや、Scala標準Futureとのinteropなど、何かと痒いところに手が届く
Monix3系からCatsに依存してしまっている。
失敗型が Throwable であるため、エラーを独自定義型の戻り値で表現したい場合使いにくい。

More Related Content

Monix Taskが便利だという話