COMPOSABLE
CALLBACKS & LISTENERS
@OE_UIA
Who am I ?
Taisuke Oe (@OE_uia) https://github.com/taisukeoe/

AndroidアプリをScalaで作ってる人

ScalaMatsuri運営してます。

最近はND4s / DL4s contributor
本日のテーマ
CallbackとListener
Callback
trait SimpleCallback[-T, -E <: Throwable] {
def onSuccess(t: T): Unit
def onFailure(e: E): Unit
}
trait SNSClient{
def getProfileAsync(url: String, callback: SimpleCallback[String,
Exception]): Unit = ???
}
* 何らかのイベントの完了時に、(一般的には?)一度だけ
呼び出される処理。
* 非同期な関数に引数として渡される
* Non Blocking
Listener
trait OnClickListener{
def onClick(b:Button):Unit
}
trait Button{
def setOnClickListener(l:OnClickListener):Unit
}
* 何らかのイベントが発生する度に呼び出される処理
* イベントを発生させるオブジェクトに予め登録する
* Non Blocking
CallbackとListener
* 非同期でNon Blockingな処理をするのに便利な仕組み
* 便利故に、JavaやJavascriptのAPIには大量に れている
* 故に、複雑なイベント処理をしようとすると…
_人人人人人人人人_
> Callback地獄 <
 ̄Y^Y^Y^Y^Y^Y^Y ̄
Button.setOnClickListener(new OnClickListener {
override def onClick(b: Button): Unit = SNSClient.getProfileAsync("https://facebook.com/xxx",
new SimpleCallback[String, Exception] {
override def onSuccess(json: String): Unit =
SNSJSONParser.extractProfileUrlAsync(json, new SimpleCallback[String, Exception] {
override def onSuccess(profileUrl: String): Unit =
SNSClient.getImageAsync(profileUrl, new SimpleCallback[Array[Byte], Exception] {
override def onSuccess(t: Array[Byte]): Unit = println(t)
override def onFailure(e: Exception): Unit = e.printStackTrace()
})
override def onFailure(e: Exception): Unit = e.printStackTrace()
})
override def onFailure(e: Exception): Unit = {
e.printStackTrace()
   SNSClient.getProfileAsync("https://twitter.com/xxx", new SimpleCallback[String, Exception] {
override def onSuccess(json: String): Unit =
* 1. Buttonがクリックされる
* 2. ユーザーのSNSプロフィールのJSONを取得
* 2-2. 失敗後、他のSNSからJSON取得
* 3. JSONをParseして、プロフィール画像のURL取得
* 4. URLから画像データのByte列を取得
Button.setOnClickListener(new OnClickListener {
override def onClick(b: Button): Unit = SNSClient.getProfileAsync("https://facebook.com/xxx",
new SimpleCallback[String, Exception] {
override def onSuccess(json: String): Unit =
SNSJSONParser.extractProfileUrlAsync(json, new SimpleCallback[String, Exception] {
override def onSuccess(profileUrl: String): Unit =
SNSClient.getImageAsync(profileUrl, new SimpleCallback[Array[Byte], Exception] {
override def onSuccess(t: Array[Byte]): Unit = println(t)
override def onFailure(e: Exception): Unit = e.printStackTrace()
})
override def onFailure(e: Exception): Unit = e.printStackTrace()
})
override def onFailure(e: Exception): Unit = {
e.printStackTrace()
   SNSClient.getProfileAsync("https://twitter.com/xxx", new SimpleCallback[String, Exception] {
override def onSuccess(json: String): Unit =
Callback地獄とは?
* Callback(やListener)の多重ネスト
* ネストを外そうとCallbackをまとめると、似たような処
理が繰り返されがちでDRYに保ちにくい
というジレンマ
* 非同期なJava APIにありがち
* 特に、AndroidなどGUI / クライアント側アプリでありがち
 再掲
_人人人人人人人人_
> Callback地獄 <
 ̄Y^Y^Y^Y^Y^Y^Y ̄
Button.setOnClickListener(new OnClickListener {
override def onClick(b: Button): Unit = SNSClient.getProfileAsync("https://facebook.com/xxx",
new SimpleCallback[String, Exception] {
override def onSuccess(json: String): Unit =
SNSJSONParser.extractProfileUrlAsync(json, new SimpleCallback[String, Exception] {
override def onSuccess(profileUrl: String): Unit =
SNSClient.getImageAsync(profileUrl, new SimpleCallback[Array[Byte], Exception] {
override def onSuccess(t: Array[Byte]): Unit = println(t)
override def onFailure(e: Exception): Unit = e.printStackTrace()
})
override def onFailure(e: Exception): Unit = e.printStackTrace()
})
override def onFailure(e: Exception): Unit = {
e.printStackTrace()
   SNSClient.getProfileAsync("https://twitter.com/xxx", new SimpleCallback[String, Exception] {
override def onSuccess(json: String): Unit =
Callback地獄つらい
* 多重ネストつらい…
* DRYじゃないのもつらい…
でも、本当に問題なのは何だろう?
CallbackとListenerが
composableじゃないこと
合成可能にするための候補
Scala標準 Promise,Future

RxScala Observable

Scalaz Task

Scalaz 継続モナド(ContT)

Scalaz Freeモナド
Composableな
Callback/Listenerができれば
出来るだけ小さな単位でCallback/Listenerを定義

複雑なイベントは、Callback/Listenerのネストではなく合
成で表現

(あと、failoverが楽だと良し)
候補
Callback Listener エラー処理 備考
Scala標準

Future/Promise
RxScala

Observable
Scalaz

Task
Scalaz

ContT
Scalaz

Free
Scala標準のFuture
scala.concurrent.Future

非同期でNon-Blockingな処理を簡便に行うための便利ツール

Future[+T]#flatMap[S](f:T=>Future[S]):Future[S] により他のFuture同士と合成可能

flatMapなのでfor-comprehensionで合成を表現可能

エラー処理を簡便に行うための関数群(e.g. recoverWith, onFailure)

Future#applyで生成する他、Promiseオブジェクトを通じて値を書き込むことができる
CallbackをFuture化する
def profileImg(imgUrl: String): Future[Array[Byte]] = {
val p = Promise[Array[Byte]]()
val f = p.future
SNSClient.getImageAsync(imgUrl, new SimpleCallback[Array[Byte], Exception] {
override def onSuccess(imgData: Array[Byte]): Unit =
p.success(imgData)
override def onFailure(e: Exception): Unit =
p.failure(e)
})
f
}
Futureのエラー処理
val json = profileJsonFuture(“https://facebook.com/xxx")
.recoverWith { case t =>
t.printStackTrace()
profileJson("https://twitter.com/xxx")
}
Future化したものを合成
val dataFuture: Future[Array[Byte]] =
for {
json <- profileJsonFuture(“https://facebook.com/xxx")
.recoverWith { case t =>
t.printStackTrace()
profileJsonFuture("https://twitter.com/xxx")
}
imgUrl <- parseFuture(json)
data <- profileImgFuture(imgUrl)
} yield data
 再掲
_人人人人人人人人_
> Callback地獄 <
 ̄Y^Y^Y^Y^Y^Y^Y ̄
Button.setOnClickListener(new OnClickListener {
override def onClick(b: Button): Unit = SNSClient.getProfileAsync("https://facebook.com/xxx",
new SimpleCallback[String, Exception] {
override def onSuccess(json: String): Unit =
SNSJSONParser.extractProfileUrlAsync(json, new SimpleCallback[String, Exception] {
override def onSuccess(profileUrl: String): Unit =
SNSClient.getImageAsync(profileUrl, new SimpleCallback[Array[Byte], Exception] {
override def onSuccess(t: Array[Byte]): Unit = println(t)
override def onFailure(e: Exception): Unit = e.printStackTrace()
})
override def onFailure(e: Exception): Unit = e.printStackTrace()
})
override def onFailure(e: Exception): Unit = {
e.printStackTrace()
   SNSClient.getProfileAsync("https://twitter.com/xxx", new SimpleCallback[String, Exception] {
override def onSuccess(json: String): Unit =
だいぶ楽になった…
でもここで一つ問題が
Future / Promiseの注意点
Promise Futureでも1度しか書き込めないため、複数回呼ばれうるListenerには使えない

def onClickFuture(button:Button): Future[Button] = {
val p = Promise[Button]()
val f = p.future
button.setOnClickListener(new LoggingOnClickListener {
override def onClick(b: Button): Unit = {
super.onClick(b)
p.success(b)
}
})
f
}
val clickFuture:Future[Button] = onClickFuture(Button)
//clickFuture succeeds
Button.click()
Button.click()
/*
[error] (run-main-2) java.lang.IllegalStateException: Promise already completed.
*/
まとめ
Callback Listener エラー処理 備考
Scala標準

Future/Promise
⃝ ☓ ⃝
Scala標準なので、依存
ライブラリが増えない
RxScala

Observable
Scalaz

Task
Scalaz

ContT
Scalaz

Free
RxScala Observable
非同期なイベントストリームを扱うためのライブラリ

Listener及びCallbackを、イベントストリーム(Observable)に見立てる

Observable[+T]#flatMap[U](f:T=>Observable[U]):Observable[U]により
Observable同士で合成可能

onErrorResumeNext[T](f:Throwable => Observable[T]):Observable[T]
で、エラー処理

Observable.from[T](f:Future[T]):Observable[T]で、Futureから
Observable生成可能
(余談)ReactiveXのDocにも…
Callbacks Have Their Own Problems

Callbacks solve the problem of premature blocking on
Future.get() by not allowing anything to block. They are
naturally efficient because they execute when the
response is ready.

But as with Futures, while callbacks are easy to use
with a single level of asynchronous execution, with
nested composition they become unwieldy.

http://reactivex.io/intro.html
ListenerをObservable化
def onClickObs(button: Button): Observable[Button] =
Observable { asSubscriber =>
button.setOnClickListener(new OnClickListener {
override def onClick(b: Button): Unit = {
super.onClick(b)
asSubscriber.onNext(b)
}
})
}
Observableのエラー処理
val json:Observable[String] =
profileJson(“https://facebook.com/xxx")
  .onErrorResumeNext
    { t =>
 t.printStackTrace()
 profileJson("https://twitter.com/xxx")
  }  
Observable同士を合成
val dataObservable: Observable[Array[Byte]] =
for {
_ <- onClick(Button)
json <- Observable.from(profileJson(“https://facebook.com/xxx")
.recoverWith
{ case t =>
t.printStackTrace()
profileJson("https://twitter.com/xxx")
})
imgUrl <- Observable.from(parse(json))
data <- Observable.from(profileImg(imgUrl))
} yield data
RxScala Observableの
メリット・デメリット
メリット

Callback, Listenerを統一的なインターフェースで扱える

ストリーム処理をしたくなっても、同じ型のまま扱える

デメリット

(少なくとも標準では)Monadではない。

(少なくとも標準には)MonadTransformerがない。

https://github.com/everpeace/rxscalaz

ObservableのMonadなどの型クラスインスタンス各種と、MonadTransformer有。
まとめ
Callback Listener エラー処理 備考
Scala標準

Future/Promise
⃝ ☓ ⃝
Scala標準なので、依存
ライブラリが増えない
RxScala

Observable
⃝ ⃝ ⃝
Scala標準Futureと

相互運用可能。

モナド化、モナドトランス
フォーマー化可能。
Scalaz

Task
Scalaz

ContT
Scalaz

Free
Scalaz Task
scalaz.concurrent.Task[+A]

Task.async[A](register: ((Throwable / A) => Unit) => Unit):
Task[A]という、callbackをラップするための関数がある。
Listenerについても使える。

非同期でNon-Blockingな処理を簡便に行うためのモナド

flatMap有り 

handleWith[B>:A](f:
PartialFunction[Throwable,Task[B]]):Task[B]などによるエ
ラー処理
Scalaz Task
Scala標準のFutureとは違い、Taskインスタンスを生成し
てもrunAsyncなどを明示的に呼び出すまで計算されない

Task.forkにより明示的に異なる論理スレッドで実行可能

その他Scalazの便利関数が大量に。

参考: Scalaz Task - the missing documentation

http://timperrett.com/2014/07/20/scalaz-task-the-
missing-documentation/
Scalaz Task化したCallback
def profileJsonTask(url: String): Task[String] =
Task.async[String] {
f =>
SNSClient.getProfileAsync(url, new SimpleCallback[String, Exception] {
override def onSuccess(json: String): Unit = f(/-(json))
override def onFailure(e: Exception): Unit = f(-/(e))
})
}
dataTask.runAsync {
case /-(data) => println(data)
case -/(e) => e.printStackTrace()
}
Task.async[A](register: ((Throwable / A) => Unit) => Unit): Task[A]
Task化したcallbackを合成
val dataTask: Task[Array[Byte]] = for {
_ <- onClickTask(Button)
json <- profileJsonTask(“https://facebook.com/xxx")
   .handleWith { case t =>
   t.printStackTrace()
  profileJsonTask("https://twitter.com/xxx")
 }
imgUrl <- parseTask(json)
data <- profileImgTask(imgUrl)
} yield data
dataTask.runAsync {
case /-(data) => println(data)
case -/(e) => e.printStackTrace()
}
まとめ
Callback Listener エラー処理 備考
Scala標準

Future/Promise
⃝ ☓ ⃝
Scala標準なので、依存
ライブラリが増えない
RxScala

Observable
⃝ ⃝ ⃝
Scala標準Futureと

相互運用可能。

モナド化、モナドトランス
フォーマー化可能。
Scalaz

Task
⃝ ⃝ ⃝
Scalaz

ContT
Scalaz

Free
Scalaz ContT
Scalazの継続モナド(のMonad Transformer)

ある処理の後続の処理を継続(Continuation)として渡すス
タイル(継続渡し、CPS)をモナド化したもの

ContT.apply[M[_],R,A](f:(A => M[R]) =>
M[R]) :ContT[M[_],R,A] で生成

エラー処理はM[_] (今回はFuture)に移譲
Scalaz ContT
PureScript作者Phil FreemanがCallback地獄をContTで
解決する記事を書いている

原文

https://leanpub.com/purescript/read

日本語訳

http://hiruberuto.bitbucket.org/purescript/
chapter12.html
Listener/CallbackをContT化
type Callback[T] = ContT[Future, Unit, T]
object Callback {
def apply[T](f: (T => Future[Unit]) => Future[Unit]): Callback[T] =
ContT.apply[Future, Unit, T](f)
}
def onClickCont(button: Button): Callback[Button] =
Callback { f =>
button.setOnClickListener(new LoggingOnClickListener {
override def onClick(b: Button): Unit = {
super.onClick(b)
f(b)
}
})
Future.successful(Unit)
}
import ScalaStdFutureExample._
def profileImgCont(imgUrl: String): Callback[Array[Byte]] =
Callback(profileImgFuture(imgUrl).flatMap(_))
ContTのエラー処理
type Callback[T] = ContT[Future, Unit, T]
object Callback {
def apply[T](f: (T => Future[Unit]) => Future[Unit]): Callback[T] =
ContT.apply[Future, Unit, T](f)
}
def recoverCont[T](failedCont: Callback[T], recover: => Future[T]): Callback[T] =
Callback {
f => failedCont.run(f).recoverWith {
case t => t.printStackTrace()
recover.flatMap(f)
}
}
ContT化したCallbackを合成
val dataCont:Callback[Array[Byte]] = for {
b <- onClickCont(Button)
json <- recoverCont(profileJsonCont(“https://facebook.com/xxx"),
profileJsonFuture("https://twitter.com/xxx"))
imgUrl <- parseCont(json)
data <- profileImgCont(imgUrl)
} yield data
dataCont.run { ba =>
println(ba)
Future.successful(Unit)
}
まとめ
Callback Listener エラー処理 備考
Scala標準

Future/Promise
⃝ ☓ ⃝
Scala標準なので、依存
ライブラリが増えない
RxScala

Observable
⃝ ⃝ ⃝
Scala標準Futureと

相互運用可能。

モナド化、モナドトランス
フォーマー化可能。
Scalaz

Task
⃝ ⃝ ⃝
Scalaz

ContT
⃝ ⃝ △
Scalaz

Free
Scalaz Free
ScalazのFree

Functorをモナド化して扱うための仕組み

Coyonedaを使うと、1階のカインドの型をFunctor化できる

故に、1階のカインドの型をモナド化できる!(Operational
モナド)

… というのを、吉田さんが書いたサンプルを見て勉強しま
した
Freeモナド化したCallback
sealed abstract class Program[A] extends Product with Serializable
final case class OnClick(button: Button) extends Program[Button]
final case class ProfileImage(imageUrl: String) extends Program[Array[Byte]]
final case class ProfileJson(url: String) extends Program[String]
final case class ParseJson(json: String) extends Program[String]
import ScalazTaskExample._
val interpreter: Program ~> Task =
new (Program ~> Task) {
override def apply[A](fa: Program[A]) = fa match {
case OnClick(button) => onClickTask(button)
case ProfileImage(imageUrl) => profileImgTask(imageUrl)
case ProfileJson(url) => profileJsonTask(url)
case ParseJson(json) => parseTask(json)
}
}
val task: Task[String] = Free.runFC(liftFC(ProfileJson(“https://facebook.com/xxx"))(interpreter)
task.runAsync {
case /-(data) => println(data)
case -/(e) => e.printStackTrace()
}
Freeモナド化したCallback
のエラー処理
def getTaskFrom(interpreter: Program ~> Task): Task[Array[Byte]] =
for {
json <-
Free.runFC(
for {
_ <- liftFC(OnClick(Button))
json <- liftFC(ProfileJson("https://facebook.com/xxx"))
} yield json
)(interpreter).handleWith { case t =>
t.printStackTrace()
profileJsonTask("https://twitter.com/xxx")
}
data <-
Free.runFC(
for {
imgUrl <- liftFC(ParseJson(json))
dt <- liftFC(ProfileImage(imgUrl))
} yield dt
)(interpreter)
} yield data
まとめ
Callback Listener エラー処理 備考
Scala標準

Future/Promise
⃝ ☓ ⃝
Scala標準なので、依存
ライブラリが増えない
RxScala

Observable
⃝ ⃝ ⃝
Scala標準Futureと

相互運用可能。

モナド化、モナドトランス
フォーマー化可能。
Scalaz

Task
⃝ ⃝ ⃝
Scalaz

ContT
⃝ ⃝ △
Scalaz

Free
 - - -
interpreterの差し替えが
簡単
まとめ
CallbackやListenerをモナドなどでcomposableにすると、DRYで再
利用可能性が上がり使い勝手がよくなる

何を使うべきかは場合にもよるが、趣味も…?

ひとまずScala標準のFuture/PromiseでCallbackだけcomposable
にしておいて、後で必要に応じてscalaz.ContT化するとか

単体で使うならscalaz.concurrent.TaskがCallbackとListenerを統
一したインターフェースで扱えるので便利かなとか

今回使用したコードはこちら

https://github.com/taisukeoe/ScalaFPEvent
Future Work
3つ以上関数をもつ、複雑なCallbackへの対応(Prism?)

Listenerで状態を扱えるようにする

Composable Callbacks & Listeners