visible true

技術的なメモを書く

Structured concurrencyってなんなの

Structured concurrency – Roman Elizarov – Mediumでは、Kotlin coroutine 0.26.0でCoroutineScopeを導入した経緯などが書かれているんですが、タイトルのStructured concurrencyってのが結局なんなのか本文中にはあんまり出てこないんですよね。

んで、CoroutineScopeの動作の理解に精一杯で、coroutineScope関数がなんなのか、どういう時に使うべきなのかよくわからないし、ましてやsuperVisorScope関数なんて言われた日には意味が不明なわけです。 とりあえずlaunch関数内でasync関数呼び出したら駄目なんだな〜ふ〜んなんで?

Notes on structured concurrency, or: Go statement considered harmfulを頑張って読む

Structured concurrency – Roman Elizarov – MediumのFurther readingにNotes on structured concurrency, or: Go statement considered harmful — njs blogを読むことを強く勧める!と書いています。パッと開くと超長いので心が折れかけるんですが読んでみたら面白かったのでメモします。

並行性の構造化

Structured concurrencyは概ね 並行性の構造化 って訳すとよさそうです。 構造化プログラミングはgotoを駆逐するために考えられたものですが、並行性の構造化は並行処理におけるgoto的なものを駆逐するための議論みたいです。 構造化プログラミングは順次, 反復, 分岐の構造を組み合わせてプログラム書くことでgotoは無くせるしむしろいい感じになるぜって話ですね。

並行性の中のgoto

エントリではgo statement(goroutineの起動)はgotoみたいなもんじゃ!って言ってます。go statementに限らず並行処理を起動するものはどれも同じみたいです。 たとえばtry-with-resources(これはJava)の中でスレッドを起動してその中でリソースにアクセスしたらどうなるか?ぶっ壊れます。 gotoじゃねえか!ということだそうです。でも確かにわかる気もする。

Trioの例

https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/#nurseries-a-structured-replacement-for-go-statements では、並行性についても同じように並行性の構造のモデルを定義して取り入れるといいんじゃないか的な話と実際にpythontrioというライブラリの說明が始まります。

とりあえず図としてはこれがわかりやすいんじゃないかと思います。ほかのパターンもあるのかはちょっと分からないですがCoroutineScopeを理解するには十分かなと思います。

f:id:sys1yagi:20181212220527p:plain:w300

fork/joinに似てますけど実際に影響を受けてるらしいです。つまりどっから並行性が始まってどこで終わるんや、ってことを明示的に書けってことですね。これCoroutineScopeですね。こういう感じ。

suspend fun startParallel() = coroutineScope { // 並行性の分岐の根本
    // 3並行に分かれる
    val a = async { requestA() }
    val b = async { requestB() }
    val c = async { requestC() }

    // 合流する
    a.await() + b.await() + c.await()
}

タイミングがずれるケースとか複雑に分岐するケースとかもあるとおもうんですが、fork/joinの根本と合流がcoroutineScopeで適切に切られていればOKってことですね〜

例外の伝搬とCoroutineScope

例外の伝搬についてもfork/joinの図がわかりやすいですね。分岐の何処かが死んだら全体が即座に死ぬ。並行実行の単位がスコープってことですかね〜。 launch関数内でスコープを切らずにasync関数呼び出したら駄目なのは、async関数が並行性の分岐を表すからで、スコープを切らない場合はforkの根本がlaunch関数の親のスコープになるから、と說明できそうです(図を書いてみたもののカチッとしなかったので省略)。

val job = Job()
val scope = CoroutineScope(Dispatchers.Main + job)
// asyncがエラーになるとscopeがキャンセルされる
scope.launch {
  val a = async { requestA() }.await()
}

終わりに

とりあえず雑ですがメモです。大分わかってきました。まだ理解できてないとこもありそうですが.. ざっくりしか読んでないからまた何度か読みます。 Notes on structured concurrency, or: Go statement considered harmful — njs blog

Roomはどのようにsuspend関数を実現しているのか

Roomに2.1.0-alpha03からsuspend関数(コルーチン)のサポートが入りましたね。ちょっと気になったのでいくらか触ってみました。

Architecture Components Release Notes  |  Android Developers

f:id:sys1yagi:20181209155920p:plain

何ができるようになるのか

DAOでsuspend関数を宣言できるようになりました。具体的には次です。

@Dao
interface UserDao {
    @Insert
    suspend fun insert(user: User)

    @Query("SELECT * FROM user")
    suspend fun getAllUsers(): List<User>

    @Delete
    suspend fun delete(user: User)
}

CoroutineScopeのなかでこれらの関数を呼ぶとノンブロッキングで各種操作ができるようになります。

val job = Job()
val scope = CoroutineScope(Dispatchers.Main + job)
scope.launch {
    try {
        val dao = db.userDao()
        0.until(100).forEach {
            dao.insert(User())
        }
        val size = dao.getAllUsers().size // 100
        // do something
    } catch (e: java.lang.Exception) {
        // error
    }
}

便利ですね。

RoomはJavaコードを生成するライブラリ

さて、そこで気になるのは、Kotlinオンリーの機能であるはずのsuspend関数をRoomはどのように生成しているのだろう?ということです。実際に生成されたコードを読んでみました。

Continuationを明示的に受け取る関数をJavaで実装している

例えば、suspend fun insert(user: User)の生成コードは次のようになっています。

@Override
public Object insert(final User user, final Continuation<? super Unit> p1) {
    return CoroutinesRoom.execute(__db, new Callable<Unit>() {
        @Override
        public Unit call() throws Exception {
        __db.beginTransaction();
            try {
                __insertionAdapterOfUser.insert(user);
                __db.setTransactionSuccessful();
                return kotlin.Unit.INSTANCE;
            } finally {
                __db.endTransaction();
            }
        }
    }, p1);
}

第二引数にfinal Continuation<? super Unit> p1 !! なるほどCPSで渡されるContinuationを明示的に受け取る関数を宣言しています。これでsuspend関数として呼び出される関数をJavaでも宣言できるんですね〜すごい。

CoroutinesRoom objectによるブリッジ

次に注目なのはinsert関数の冒頭で呼び出しているCoroutinesRoom.execute関数です。この中身を覗くと、次のようになっています。

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
class CoroutinesRoom {
    companion object {
        @JvmStatic
        suspend fun <R> execute(db: RoomDatabase, callable: Callable<R>): R {
            return withContext(db.queryExecutor.asCoroutineDispatcher()) {
                callable.call()
            }
        }
    }
}

@JvmStaticを宣言してJavaからstatic関数として呼び出せるようにしています。CoroutinesRoomはKotlinなのでsuspendキーワードが使えます!そこでwithContext関数を呼び出してqueryExecutorを使ってクエリを実行しています。suspend関数をJavaで実装して、さらにKotlinに引き渡してwithContext関数を使って実行を別のスレッドに切り替えつつ中断/再開を実現しています。

呼び出し元を見直すと次のようになっています。

CoroutinesRoom.execute(__db, new Callable<Unit>() {...省略...}, p1)

p1はContinuationです。JavaからKotlinのsuspend関数を呼び出すには末尾にContinuationを明示的に渡せばいいんですね〜すごい。

withContextとExecutor

withContext - kotlinx-coroutines-core

withContext関数は指定したContextで渡したラムダ式の中断/再開を行います。Roomではdb.queryExecutorをCoroutineDispatcherに変換して渡してます。デフォルトだと自動的にRoomが用意したExecutorが使われてしまうのですが、queryExecutorはRoomDatabase.Builderでセットできます。

次の例はDispatchers.IOをRoomのqueryExecutorにセットする例です。

val db = Room.databaseBuilder(
            applicationContext,
            AppDatabase::class.java,
            "database-name"
        ).apply {
            // ※良い方法ではない気がするのでマネはしないほうがいいかも
            val dispatcher = Dispatchers.IO
            if (dispatcher is ExecutorCoroutineDispatcher) {
                setQueryExecutor(dispatcher.executor)
            }
        }.build()

すごいぞRoom

コルーチンを主に使っているのでこういったサポートはとてもたすかります。 あとJavaによるsuspend関数の橋渡しのアイデアはすごいと思った。わいわい。

Kotlin Coroutine 1.0.0までに夏から変わったところ

日進月歩。夏に話した頃とは大きく変わってしまったKotlinコルーチン。

Kotlin Fest 2018でコルーチンの話をしてきた - visible true

↑はコルーチンの概念と実現の仕組みを中心に置いたので、その辺はまぁ変わってないんで問題ないんですが、後半の実際にコルーチンを使う場面に関してはすでにdeprecatedっていうか、そのまま書いても動かないくらい変化してしまいました。

ということでざっくりメモ

0.25.xから1.0.0までの主要な変化

基本的にはリリースノートと関連するissueの議論を読めば大体わかります。 https://github.com/Kotlin/kotlinx.coroutines/releases

[0.26.0] CoroutineScopeを導入し既存の書き方がdeprecatedに

9/12にリリース。kotlin festから2週間やん。

CoroutineScopeという概念が追加された。既存のコルーチンはすべてGlobalScopeとして扱うことになった。そのほか構造的並行性(Structured concurrency)が基本的な考え方となり、スコープの階層を適切に構築しようなみたいな話になったぽい。

もともとは全部グローバルコルーチンだったので単純だけどリークの可能性もあった(永遠に生きるコルーチンとかのケアがあんまりなかった)が、構造的並行性によってrootスコープから末端まで親子関係を適切に構築することでライフサイクルの伝搬をいい感じにしていこうなという感じ。

parent jobで親子関係をやりくりしていたのを、CoroutineScopeでデフォルトで必ず親子になるようにしたため、書きやすくなったと言えるし設計もしやすくなったなと思う。大変だけど。

ちょっと追記

CoroutineScopeによってコルーチンのライフサイクルを構築しやすくなった。これによってAndroidとの親和性が上がったと言える。ActivityのライフサイクルやFragmentのライフサイクルなどに合わせてそこにぶら下がるコルーチン全体を停止できるようになるから。なのでコルーチンをガシガシ書ける空間(Activity, Fragment, ViewModelなどのライフサイクルに合わせて動くCoroutineScopeを実装できる)を提供できて、アプリに導入するハードルが下がる。実際CoroutineScope導入後にJetpackコンポーネントでどんどんコルーチンサポートが入ってきている。この点はすごく良かったのでは〜って思う。

[0.26.0] Dispatchersを導入

CoroutineScopeの動きとは別でDispatcherの名前がバラバラ(CommonPool, UI, JavaFx...)なのでなんとかしない?みたいな議論のなかでDispatchersオブジェクトが導入され、そこに生やす形になった。

  • CommonPool -> Dispatchers.Default
  • UI -> Dispatchers.Main
  • _ -> Dispatchers.IO

など。まぁこれは名前変わったね程度の理解で大体よさそう。CommonPoolはいずれなくなるらしいけどまぁよしなにやってくれるみたいなので頼むみたいな感想。

[0.30.0] 例外伝搬の仕様が変わった

9/29リリース。矢継ぎ早〜

コルーチンが例外を投げたときにどこまでどう伝搬するかみたいな話。たとえば次のコード

suspend fun foo() = coroutineScope<Unit> {
  val one = async {
    delay(Int.MAX_VALUE) // suspend until cancellation
  }

  val two = async { throw MyException() }

  one.await()
}

oneとtwoが同時に動きだすけどtwoはすぐに例外を起こす。しかしDeferredはawait()しないと例外が飛ばないので、oneが長いとtwoが死んでるのにずっと待っちゃうみたいな動きは問題だ、ってことで、即時に親をキャンセルする形になった。

これが怖いのは次ようなコードは書いちゃ駄目になったということ

val job = Job()
val scope = CoroutineScope(Dispatchers.Main + job)
scope.launch {
  try {
    val a = async { // launchの中でasync起動したらあかん
      delay(1000)
      throw Exception()
    }.await()
  } catch (e: java.lang.Exception) {
    e.printStackTrace()
  }
}

launchとasyncは親子関係を持っているがasyncは親をcancelにするのでlaunchもcanceledになり、さらにその上まで例外を伝搬する。アプリでこういう呼び出しをしていると例外補足できずにクラッシュしてしまう。

明確な理由はわからないが、スコープビルダー(coroutineScope{})は親をキャンセルしないことになっていて、次のように書けば解決する。

val job = Job()
val scope = CoroutineScope(Dispatchers.Main + job)
scope.launch {
  try {
    // ※クラッシュしないけどこのケースでは非推奨なのでこれは使わないでください
    coroutineScope { 
      async {
        delay(1000)
        throw Exception()
      }
    }.await()
  } catch (e: java.lang.Exception) {
    e.printStackTrace()
  }
}

ただほとんどのワンショット継続はwithContext(Dispatchers.IO)でまかなえるので次に書くようにしたほうがよさそう

val job = Job()
val scope = CoroutineScope(Dispatchers.Main + job)
scope.launch {
  try {
    withContext(Dispatchers.IO) {
        delay(1000)
        throw Exception()
    }
  } catch (e: java.lang.Exception) {
    e.printStackTrace()
  }
}

coroutineScope{}の使い所はスコープを切り換える時、つまりそのスコープの中で別のコルーチンを起動する時。async複数呼び出して並行で実行しまとめて待ち合わせる場合はcoroutineScope{}を使う必要がありそう。

この辺ややこしいし言語化するの大変なので大幅に省くけど、さらにSupervisorという考え方も導入されて混乱に拍車がかかっている。Supervisorはスコープ内のコルーチンがエラーになっても親がキャンセルされずに、他の子コルーチンを継続するやつ。実際使う機会はAndroidではほとんどないよな〜というのが個人的な見解。並行で複数リクエストするけど投機的で相互の失敗に依存しないケースとか。

[1.0.0] experimental packageを廃止 🎊

10/30 🎊

https://github.com/Kotlin/kotlinx.coroutines/releases/tag/1.0.0

experimentalパッケージからはずれてkotlinx.coroutinesになった!めでたい。 マイグレーションも1.3マイグレーションの中でやってくれるので移行も楽。楽しい。

おわりに

リリースノートはしっかり読んだほうがよいですね 😵

ノンアルコールビールで晩酌すると風呂上がりにさっぱりプログラミングできて助かる

adventar.org

タイトルですでに完結していますが。

アルコール飲むと寝る前にプログラミングできない

年齢の所為もあるんでしょうけど、夕飯時にアルコール飲んじゃうと風呂上がって、さ〜てプログラム書こうかってなっても酔っ払っているのでついつい漫画読んだり映画見たりゲームしたりしてゴロゴロしてあーまぁアルコール入れたしもう寝るか〜みたいになってプログラミングせずに寝がちなんですよね。

じゃあ飲まなければいいのでは?という事なんですが、しかし、アルコールっておいしいですよね。ビールだとサントリー ザ・プレミアム・モルツ 〈香る〉エールとか好きなんですよ。なので飲みたいは飲みたい。でもプログラミングもしたい。

ノンアルコールビールを飲むと酔った気がするけど実際は酔ってない

当たり前なんですがノンアルコールビールはビールの味わいやのどごしがありながらもノンアルコールなので酔わないんですよ。ところが飲んで見るとカーッとくるものがあってあたかもアルコール飲んでる感覚があるんですよね。プラシーボ的ななにかなんでしょうか?ということで割とノンアルコールビールでも体験としてはビールとそんなに変わりがないんですよね。で、飲んで夕飯食べてつまみも食べてあ〜飲んだ呑んだって満足したあと風呂入って机に向かうと全く飲んでない状態に復帰できるので問題なくプログラミングができるわけです。

ノンアルコールビールは以前は飲めたものじゃない感じがあったんですが最近はいろいろ発達していて大分美味しく飲めるようになりました。個人的にはアサヒ ドライゼロが好きです。

ノンアルコールビールで晩酌すると風呂上がりにさっぱりプログラミングできて助かる

肝臓にも負担かからないし、酔わないし、プログラミングもできて良いっす。

Robolectricで複数のLocaleをテストしたい時はEnclosedで頑張るしかないのか

RobolectricでLocaleを指定するには@Config(qualifiers)を使う

テストで特定のLocaleで実行したいときってありますよね。Robolectric*1だと@Config(qualifiers)*2を使うことでテストのLocaleを指定できます。

@RunWith(RobolectricTestRunner::class)
@Config(qualifiers = "zh-rCN")
class HogeTest {

ただこれだとテストケース全体に適用されるので、ある言語の時だけ特殊な表記をするケースなんかを書くのには適しません。

複数のLocaleで回したい場合はEnclosedを使う?

Enclosedを使えば一つのクラスのなかで複数のテストクラスを書けるので、qualifiersを分けてテストを書けます。

@RunWith(Enclosed::class)
class HogeTest {
  @RunWith(RobolectricTestRunner::class)
  @Config(qualifiers = "ja")
  class JaTest {
  }

  @RunWith(RobolectricTestRunner::class)
  @Config(qualifiers = "zh-rCN")
  class CnTest {
  }
}

しかし...

いちいちクラス切るのめんどくさいなぁ

同じテスト内容でロケールだけ変えて結果の違いをテストしたいみたいな場合にもいちいちクラス切って書かないといけないのでめんどくさいな〜ということでどうしたらいいんだろう。なにかいい方法あるかな〜って思った話です。

*1:4.0-beta-1を使ってますがそれ以前でも使えるはずです

*2:locale以外にもいろいろ指定できます http://robolectric.org/using-qualifiers/

mockkとかでモック書く時にalsoと組み合わせると読みやすくなりそうな気がした

mockk便利ですよね。suspend関数のモック*1までサポートしてるしstatic, objectのモックもサポートしているのでいろいろはかどります。

github.com

alsoと組み合わせるとよさそう

mockitoと大体同じなんですがモック作る時ってだいたいこんな感じになります。

val yearly: Sku = mockk()
every { yearly.priceAmountMicros } returns 600000

every関数はmockitoにおけるwhenみたいなもんです。returnsが中置関数になっていて.を省けるんですがまぁあんまりどうでもいいです。 ところでモックオブジェクトとその振る舞いの定義ってバラバラに書くことになっていつももやもやするんですよね。 ということでalso使ってこう書くといいんじゃないかと思いました。

val yearly = mock<Sku>().also { mock ->
  every { mock.priceAmountMicros } returns 600000
}

よく考えたらmockitoでも同じようにできそうですね。 also関数*2便利。

追記

ウボォー「たまに こういう奴が いるから やめられねェ ブログはな」 デフォルトでblock渡せるとは!ありがとうございます。

*1:coEvery, coVerifyなど

*2:apply関数でもいいんですが空間がthisになっちゃうんでalso関数のほうがいろいろ複雑に書く時に楽かなと思ってalso関数にしました

Kotlin Fest 2018でコルーチンの話をしてきた

kotlin.connpass.comでコルーチンに関する話をしてきました。

Kotlin コルーチンを理解しよう

スライドはコチラ。音声や動画が無くても大体読めばわかる内容になっていると思います。

流れ

6月末にひつじさんからお手紙がとどく

f:id:sys1yagi:20180827130403p:plain

直前の技術書典4Kotlinのコルーチンを使う前に自前で実装してみるという話を書いていたこともあり、 その辺をベースにした話の組み立てでできそうな気がするということでシュッと受けた。 結果的にめちゃくちゃ大変でしたが最終形はなんとかまとまったので良かったです。

調べたことや登壇時は触れられなかったことなど

話のベースは頭の中にあったものの、コルーチンの概念そのものが一体どういうものなのかあまり深く理解していなかったこともあり、その辺の調査もしなきゃな〜と手を動かし始めるとそのままどツボへ真っ逆さま。今回話の流れや時間的に触れられなかったことなんかをここにメモっておきます。

コルーチンとはなにか

昨年の6月頃に話したもうAndroidの非同期処理はasync/awaitでいいんじゃないかなぁと思ったでは"関数の途中で中断したり再開できるやつ"くらいにしか説明してなくて、さらにその辺は別に便利に使えるんだから気にしなくてよくないくらいの気分でいたんですが、しかしそれはそれとしてちゃんと調べるべきだよな〜と思ってとりあえずWikipediaなんかを見つつ、メルビンコンウェイの論文が初出らしいってことで論文読もうかとおもって論文を読んだ。

Design of a Separable Transition-Diagram Compiler

発表にもある通りCOBOLコンパイラの話で、コルーチンを使っていい感じにCOBOLコンパイラを記述できるぜ!がメインの話なので、コルーチンそのものは現在の説明からはかなり遠い記述になっていた。この論文では字句解析と意味解析を別モジュールで実装することで分離したプログラムがそれぞれ協調して動くということを実現しているらしい。

その後コルーチンに関する説明を調べると「協調的マルチタスク」と「プリエンプティブマルチタスク」という用語によって両者を比較する文脈があらわれるのだけど、これはコルーチンがスレッドどころかプロセスすらない時代に生まれたマルチタスクの概念で、コルーチンが論文に登場したのと同時期に「タイムシェアリング」についての研究も盛んになっていて、結果的にタイムシェアリング勢によるプリエンプティブマルチタスクとかスレッドとかが主流になってコルーチンは廃れたみたいな流れがあってそれはそれで面白いんだけどそれ道具としてのコルーチン関係なくない?みたいになってどう触れるか迷いが生じたりした。

協調的マルチタスクというのがコルーチンの重要なコアの概念なんだけどいかんせん一番シンプルな例でもproducer consumerパターンでややこしく、async/awaitで今まさに価値が見出されつつあるコルーチンの説明の頭にもってくるのしんどくないってなってそこも却下になった。どっちかっていうとKotlinコルーチンにおけるproduceコルーチンビルダー関数やChannelがまさに本来のコルーチンって感じだけど実際利用するケースってかなり少ないので、コルーチンを使っていこうなと言うにはニッチすぎて厳しいと考えて協調的マルチタスクというワードは出さないようにすることにした。もうひとつややこしいのは、実際のKotlinのコルーチンはスレッドプールで動くという点があってそうすると協調的マルチタスクプリエンプティブマルチタスクの対比がふんわりしてしまって簡潔に説明できないよなというのも悩ましい点だった。その辺の観点からFiber、グリーンスレッド、軽量スレッドとかにも触れるのはやめた。

2004年の論文にRevisiting Coroutinesというのがあって、ここで「昨今はマルチスレッドプログラミングが盛んになってコルーチンをnativeサポートする言語が減ってきてけしからん。コルーチンはいいぞ」って書いてあって元気だなって思って読んでみると、現代に通じるコルーチンの分類をしているぽくて我が意を得たりと思ったのだけど内容が難しくて、完全非対称コルーチンが多分async/awaitだよなーと思いつつでも言明はされてないしワンショット継続とかのワードもよくわからず(限定継続のこと??)、これをもって現代のコルーチン実装が整理されたとは言えず参照を断念した。

英語のWikipediaコルーチンの説明が一番端的で正しいのだけど、

コルーチンは、サブルーチンを一般化するコンピュータプログラムコンポーネントです。 特定の場所で実行を一時停止および再開するための複数のエントリポイントを許可することによって、非プリエンプティブマルチタスクを実現します。

みたいな文章が初っ端にあって、確かにいろいろ調べた今となってはこれでわかるんだけどさーこれ参照した上で説明すると終わらねーだろ〜ってなって、その後の例やサブルーチンやスレッドとの比較は良かったので取り入れたものの実際に書いてる事よりかなり平易に説明した結果厳密ではなくなってそこはそれで胃が痛い気持ちだったりした。サブルーチンを一般化するというのは同じスレッドで動く子コルーチンが中断しない場合サブルーチンと同じ動きやで、みたいなことでコルーチンですべて表現できるよねということなんだけどそこ説明するのはその後の話とは関係ないからしんどいなどなどしんどかった。

そこで55年飛ばすことにした。

最終的にはコルーチンを端的に表す文言としてCoroutines for Kotlin (Revision 3.2)での説明と日本語版Wikipediaの利用用途についてを参照することになった。公式リファレンスの説明も検討したがこちらは道具としてのコルーチンの話が厚めだったのでやめた。この話のあとにする話と関連していてかつ破綻がない情報を抜き出すのはかなり骨が折れた。今回の取り出し方は本当にギリギリだったなぁと思う。

この辺は本番2日前辺りでようやくfixした。

Kotlinはどのようにコルーチンを実現しているのか

ここについてはすでに大体調べていたのでそこまで迷いはなかった。 昨年のKotlin Confの発表を見返しつつ、CPS Transformとかステートマシンについて再確認して構成を落とし込んでいった。

時間的な問題やあるいは後半の使い方に直接関連しない点で削いだ部分が結構あってそれはそれで惜しい気はしている。実際にバイトコードを読みながら、ステートマシンとして生成されたクラスがCoroutineImplを継承していたりLambdaを実装していたりlabelとswitchで状態を分解している様子を眺めたり、except宣言された(suspend R.() -> T).createCoroutineUnchecked関数の実装と継続インターセプターが使われる様などを眺めたりしたがったが、収拾がつかない気がしたのでやめた。

suspend修飾子、継続インタフェース、CPS、コルーチンビルダー、継続インターセプターの説明は最初から入れていたが、前半のコルーチンとはなにかの説明で出たコルーチンの要素と整合する形で落とせたと思うのでよかった。が、コルーチンビルダー、継続インターセプターは完全にライブラリ実装に近い話なので基本的な使い方の文脈で説明してもよかったかもと思った。

Kotlinコルーチンの基本的な使い方

前半に力を持っていかれ、本当に基本的な部分しか触れられなかったのと練習不足でわりとグダグダだったな〜と反省するなど。ただ前半の説明を踏まえておけば最低限でも自分で進めていく力を得られると思って使い方部分はかなりあっさりにした。Guide to kotlinx.coroutines by exampleの内容を踏襲したほうがいいんじゃないかと思った瞬間もあったがrunBlockingを混乱なく説明できない気がしてやめた。

コルーチンの前提知識を得た状態とはいえストーリーとしてもっとライブラリ実装側の話とか提供されている機能群を厚めにしないとふわっとしないかと思いつつ基本形を作ってasync/awaitのバリエーションという形となった(手が回らず)。ジェネレータ、Channelやアクターの話、テストに関する話なども入れられたらなぁと思いつつ、リハしたら時間オーバーしたのでそこは手はつけなかった。

結構ギリギリで、最初はlanch関数の引数を上から順に説明したりしていたが当日スライドを見返しながら調整したりした。Jobの説明とか入ってなかったり。

とか言っていたが、この章のスライドは発表の2時間前までいじっていた。終盤のRetrofitのDeferredや、EventBus、android-coroutinesの話は全然深掘りできなかったのでちょっと残念だけどおもしろ感を出したかったので割り切り。

前日自宅でリハしたあと奥さんに「(血の気が引いて)顔が緑だよ」って言われて「グリーンスレッドだけにね」って言ったかどうかは定かではない。

当日の様子

ランチ難民をしていたがひつじさんがコーディネートしてくれて、元同僚と初対面の方との3人で行ったのだが、 初対面で知らない人と思っていたらなんとAndroid アプリ設計パターン入門で僕が抜けた穴を塞ぐべく参加して下さった吉岡さんだとわかり「その節はどうも」みたいな感じになった。お礼を言えてよかった。

寿司の様子。というかこの1枚しかこの日は撮ってなかった。 f:id:sys1yagi:20180827231351p:plain

スピーカー控室はめっちゃでかくてスピーカーが10人掛けテーブルを1人で専有しポツポツ座ってるみたいな感じで快適だった。 プロジェクターがありスライド映して試せるのかと思ったらA会場のライブが始まりビビったが面白かった。

会場ウロウロしたり控室戻ったりスライドいじったりコーヒー飲んだり自家製プディング食べたりして過ごした。 いつもは緊張でほぼ他の発表見られないのだけど控室にライブが流れるのでよかった。

本番後はAsk the Speakerで幸いにも行列ができいろいろ会話した。 RoomにDeferredくるか!?

Twitterの感想も好評のようでよかった。 懇親会ではAndroid老人会が発生して面白かった。

出し殻のようになって翌日はオクトパストラベラーをプレイし続ける一日となった。

良いもの残せたのではないかなと思うし運営も会場も最高だったのでめっちゃ良かったと思います(小並感)。参加して下さった方々ありがとうございました。