visible true

技術的なメモを書く

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老人会が発生して面白かった。

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

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

facebook/Sonarで大体Stethoを置き換える

Stetho便利ですよね〜。 最近Stethoを置き換えるfacebook/Sonarというライブラリが登場しました。 メトリクスツールのSonarとかDAWCakewalk SONARとかとかぶってて名前紛らわしいっすね〜

facebook/Sonarでできること

StethoはAndroid向けのライブラリであったのに対し、SonarAndroidiOSの両方の環境をサポートしています。 また独立したElectronアプリを提供していて、Chrome Dev Toolsをポチポチする必要がなくなりました。やったね。

f:id:sys1yagi:20180718112432p:plain

SonarにはBuilt-inのプラグインがいくつかあります。とりあえず現状あるやつを紹介しときます。

Logcat

Logs · Sonar

SonarのElectronアプリを起動するととりあえずLogcatが見れます。アプリ側で特に対応とか入れなくても適当につなぐみたいです。 あとなんか画面キャプチャとか動画撮影もやれるみたい。adb~

Layout Inspector

Layout Inspector · Sonar

Layoutの樹海に潜っていじれるやつです。

f:id:sys1yagi:20180718113315p:plain f:id:sys1yagi:20180718113318p:plain

Stethoでも出来た気がするな。どうだったかな。

Network Inspector

Network · Sonar

こちらもStethoで出来たやつ。概ねできることは同じはず。後ほど書くけどOkHttp3しかサポートしてないので、まだOkHttpを使ってる場合は使えないので注意。

f:id:sys1yagi:20180718113546p:plain

Shared Preference Viewer

Shared Preferences · Sonar

Shared Preferenceの中身を見たり編集したり。 ただ、特定のShared Preferenceを指定する必要があるので、Stethoよりpoorになった印象。

https://fbsonar.com/docs/assets/shared-preferences.png

Sandbox

Sandbox · Sonar

よくわかってないけど、key-valueを設定しておくと、Sonarアプリ側からいじれるようになる感じっぽい。 アプリの環境変数を動的にいじれるぜ的なsomethingという理解でよさそう。

facebook/Sonarでまだできないこと

できないことというか、plug-inがまだないというか..

  • SQLite database viewer
    • いずれBuilt-inでサポートされるとは思うけどまだない
  • Realm database viewer
    • 欲しい
  • no-op dependenciesがない
    • get startedではdebugImplementationで書けといいつつリリースビルド時に死にがち (プラグイン使うとガッツリコードで依存する。build-typeで分離はできる範囲だとは思う)

ちょ〜っと厳しいよねまだ..

導入手順

まあGetting Startedに書いてあるんですが、 dependenciesを追加して...

repositories {
  jcenter()
}

dependencies {
  debugImplementation 'com.facebook.sonar:sonar:0.6.11'
}

Applicationなどで初期化します。

fun initSonar() {
  SoLoader.init(this, false)
  if (BuildConfig.DEBUG && SonarUtils.shouldEnableSonar(this)) {
    AndroidSonarClient.getInstance(this).start()
  }
}

pluginを導入する

さてそのまま実行しても特になにも起こりません。pluginを入れないと。 とりあえずLayout InspectorとShared Preference Viewerは次のように設定します。

fun initSonar() {
  SoLoader.init(this, false)
  if (BuildConfig.DEBUG && SonarUtils.shouldEnableSonar(this)) {
    AndroidSonarClient.getInstance(this).apply {
      // layout inspector
      addPlugin(InspectorSonarPlugin(this@Application, DescriptorMapping.withDefaults()))

      // shared preference
      // 第二引数にpreference名を指定できる
      // 引数渡さない場合はデフォルトのpreferenceを参照する
      addPlugin(SharedPreferencesSonarPlugin(this@Application)) 
    }.start()
  }
}

Network Inspectorについてはもうちょい複雑で、pluginの初期化のほかにOkHttpClientのNetworkInterceptorにもpluginインスタンスをセットしないといけないのでちょっと大変。

val networkSonarPlugin = NetworkSonarPlugin()

fun initSonar() {
  SoLoader.init(this, false)
  if (BuildConfig.DEBUG && SonarUtils.shouldEnableSonar(this)) {
    AndroidSonarClient.getInstance(this).apply {
      addPlugin(networkSonarPlugin)
    }.start()
  }
}

OkHttpClientを作る時にNetworkSonarPluginインスタンスをどっから引っ張ってくるか..

@Provides
@Singleton
fun provideOkHttp3(): OkHttpClient {
  return OkHttpClient.Builder().apply {
    // initSonar()の時と同じインスタンスが必要
    addNetworkInterceptor(SonarOkhttpInterceptor(Application.networkSonarPlugin))
  }.build()
}

Daggerでinjectすんのがいいだろな〜。ちょっとめんどくさい。 SonarOkhttpInterceptorはOkHttp3向けにしか存在しないぽいので注意してください。

ここがいいよねSonar

アプリ起動したら自動的にNetwork Inspectorがキャプチャ開始してくれるので、 Chrome Dev Toolsで毎度立ち上げ直していた手間がなくなってその点はめっちゃ良い。 今の所それくらいかな...

hyperionの様子

SQLite database viewerが無いのはかなり厳しい気がしているので、せめて代替えなんかできないかということで、 Hyperion Androidhyperion-sqliteあったなーって思ったら、 not yet released だった hyperion-sqlite not yet released? · Issue #137 · willowtreeapps/Hyperion-Android · GitHub 諦め。

おわりに

自分は導入してしまったけど、まだ様子見な気がする。絶賛開発中なので導入よりいろいろcontributeを考えるほうがいいのかもなと思うなどした。 SQLite database viewerはちょっと試しに作りたい。

Android Studio自体のヒープサイズを変更する

Android Studioでしばらく開発していると、一文字打ったり、行移動するだけで一瞬固まるといった現象に見舞われる。CPUが定期的にスパイクしていて、待てど暮らせど改善しない。再起動すると直るがしばらく触っているとまた遅くなる。

どうもメモリが足りなくなってGCしまくっているため起こるらしい。

どうにかならんものかと調べたら公式に設定方法があったのでメモ

Android Studio の設定 | Android Studio

[Help] > [Edit Custom VM Options] で studio.vmoptions ファイルを開き( or 作り )、 -Xmx4gとか書いて再起動するといける。

やったね。

nativechainをKotlinで実装する

nativechainとは

nativechainはブロックチェーンの基本的な概念を200行のjavascriptで実装したものです。 https://github.com/lhartikk/naivechain

nativechainは次の実装および制約を持っています

  • ノードをコントロールするHttpインタフェース
  • Websocketを使ったP2Pノード
  • データ(ブロックチェーン)を永続化していない
  • Proof of WorkやProof of Stakeはない。無条件でチェーンにブロックを追加できる

Kotlinで実装する

ブロックチェーンの基本的な概念を学ぶには実装するのが手っ取り早いよね〜という事でnativechainを写経してもいいんだけど、どうせならKotlinで書いてみたいよね〜という事でKotlinで実装しました。

GitHub - sys1yagi/nativechain-kotlin: https://github.com/lhartikk/naivechain by kotlin

ブロックの実装

ブロックのデータ構造は極めてシンプルなのでdata classでシュッ

data class Block(
    val index: Long,
    val previousHash: String,
    val timestamp: Long,
    val data: String,
    val hash: String
)

ハッシュはSHA-256を使っている。JavaだとMessageDigestでやれるので楽。バイト配列をhex stringに変換するのがKotlinだと少し面倒だった。

private fun calculateHash(
  index: Long,
  previousHash: String,
  timestamp: Long,
  data: String): String {
  return MessageDigest.getInstance("SHA-256")
    .digest("$index$previousHash$timestamp$data".toByteArray(StandardCharsets.UTF_8))
    .joinToString(separator = "") { "%02X".format(it) }
}

Httpサーバの実装

なにを使うか迷ったけどJetBrains製のktorを使うことにした。

compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
compile "io.ktor:ktor-server-core:$ktor_version"
compile "io.ktor:ktor-server-netty:$ktor_version"
compile "io.ktor:ktor-websockets:$ktor_version"
compile "ch.qos.logback:logback-classic:1.2.1"

次のようにシュッとサーバを立てられる。

embeddedServer(Netty, httpPort) {
  routing {
    get("/blocks") {
      val blockchain = jsonConverter.toJson(nativeChain.blockchain)
      call.respondText(blockchain, ContentType.Application.Json)
    }

    post("/mineBlock") {
      val data = call.request.receiveContent().inputStream().bufferedReader().readText()
      val mineBlock = jsonConverter.fromJson(data, MineBlock::class.java)
      nativeChain.addBlock(nativeChain.generateNextBlock(mineBlock.data))
      webSocketServer.broadcastLatestMessage()
      call.respond(HttpStatusCode.OK)
    }

    get("/peers") {
      call.respondText(webSocketServer.sockets().joinToString(separator = "\n") { it.peer() })
    }

    post("/addPeer") {
      val data = call.request.receiveContent().inputStream().bufferedReader().readText()
      val peer = jsonConverter.fromJson(data, Peer::class.java)
      webSocketServer.connectToPeers(listOf(peer))
      call.respond(HttpStatusCode.OK)
    }
  }
}.start(wait = true)

WebSocketサーバの実装

WebSocketサーバもktorを利用。embeddedServer関数でガチャガチャ書いてると動くので便利。接続が確立されるとWebSocketSessionが渡ってくる。WebSocketSessionはincoming(ReceiveChannel)とoutgoing(SendChannel)を持っている。コルーチンなので待機とかその辺がいい感じでいい。

embeddedServer(Netty, port) {
  install(DefaultHeaders)
  install(CallLogging)
  install(WebSockets) {
    pingPeriod = Duration.ofMinutes(1)
  }
  routing {
    webSocket {
      logger.debug("receive websocket connection")
      val socket = KtorWebSocket(this)
      initConnection(socket)
    }
  }
}.start(wait = false)

WebSocketクライアントの実装

なんとktorにはWebSocketクライアントの実装がない(ぽい)。ので、P2P的な実装ができない。うへー。そこでTyrusを使うことにした。

compile "org.glassfish.tyrus.bundles:tyrus-standalone-client-jdk:$tyrusVersion"
compile "org.glassfish.tyrus:tyrus-container-grizzly-server:$tyrusVersion"

TyrusはJavaライブラリなのでわりと古めかしいインタフェースを持っている。後述するとおりサーバとクライアントで異なるWebSocketのスタックを使っているのでコネクション全体を管理するために抽象化が必要となる。ktorのWebSocketSessionはChannelでI/Oするので、こちらもChannelを持つ形で実装をした。

nativechain-kotlin/TryusWebSocket.kt at master · sys1yagi/nativechain-kotlin · GitHub

WebSocketセッションの抽象化

WebSocketサーバで接続を受けると、io.ktor.websocket.WebSocketSessionでやりとりを行う。WebSocketクライアントで接続をするとjavax.websocket.Sessionでやりとりを行う。nativechainはブロックの更新があった時などにネットワーク全体にブロードキャストを行う。このためサーバで受けた接続とクライアントとして張った接続の両方を同じように取り扱いたい。nativechainというかjavascriptはWebSocketのサーバクライアントが両方同じように作れるので複雑なことはしてないのだがktorがWebSocketサーバしか持たないのでややこしくなってしまっている。サーバもTyrusにしようか考えたけどなんかアノテーションベースでDIを前提とした設計なので諦めた。

とりあえずWebSocketInterfaceを作って、必要な関数を宣言し、

interface WebSocketInterface {
    fun send(message: String)
    fun receiveChannel(): ReceiveChannel<String>
    fun peer(): String
}

KtorWebSocketTryusWebSocketを実装した。ktorでWebSocketクライアント実装して( ゚д゚)ホスィ…。

動かす

あとは二個サーバ起動してcurlとかでどっちかを叩くとシュッとなる

./gradlew jar
java -jar build/libs/nativechain-kotlin-1.0.jar 3001 6001
java -jar build/libs/nativechain-kotlin-1.0.jar 3002 6002 ws://localhost:6001
curl -H "Content-type:application/json" --data '{"data" : "Some data to the first block"}' http://localhost:3001/mineBlock

おわり

ブロックチェーンの仕組みはなんとなくわかった。ただProof of Workとかはやってないしその他の様々なことにはまだ触れてないので実用的な何かを作るのは当然まだ無理ぽい気配。けど面白かった。分散データベースとヒステリシス署名の組み合わせだというのをどこかで見たけどたしかにその通りだとおもった。改ざんを心配しなくていいことで得られることって色々ありそうだけど具体的にここに使えそうだってのがパッと思いつかないのでもっと学習が必要だ。WebSocket周りはなんか統一したいけどめんどくさいのでまぁいいやという気持ち。

とりあえず今はこれを読んでいる。

で、この辺も読もうと考えている。

Kotlin 1.2.0 言語機能の感想

Kotlin 1.2.0出ましたね。 主に新しい言語機能についてざっくり感想を述べます。

via What's New in Kotlin 1.2

言語機能

アノテーション上でArrayリテラルをサポート

今までは次のようにarrayOfって書いていたけど、

@Singleton
@Component( modules = arrayOf(
    AppModule::class,
    DataModule::class,
    ...
  )
)
interface AppComponent : AndroidInjector<HogeApplication> {

次のように書けるようになる。

@Singleton
@Component( modules = [
    AppModule::class,
    DataModule::class,
    ...
  ]
)
interface AppComponent : AndroidInjector<HogeApplication> {

アノテーションでのみ利用可能なので、大体Dagger周りとかPermissionDispatcherとかRobolectricとか周りで活躍しそう。

lateinit が top-levelプロパティとローカル変数で利用可能に

nullableじゃないけど宣言時はまだ値が決まらない時に使えそう。 とはいえlateinitは初期化されてない状態で触るとクラッシュするので、使い所が難しい。 Activity, Fragment等のライフサイクルがあるコンポーネントのプロパティとして使う事はあるが(やむを得ず)、 top-levelやローカル変数でのユースケースってそんなにないのではないかという気はする。

公式のコード例が次。特殊〜

// A cycle of three nodes:
lateinit var third: Node<Int>

val second = Node(2, next = { third })
val first = Node(1, next = { second })

third = Node(3, next = { first })

同じことをDelegateでもできる。

var third by Delegates.notNull<Node<Int>>()

val second = Node(2, next = { third })
val first = Node(1, next = { second })

third = Node(3, next = { first })

lateinit varが初期化済みかどうか検査できるようになった

こんな感じでプロパティリファレンスで検査できる。

class A {
  lateinit var lateinitVar: String
  fun initialize () {
    if(!this::lateinitVar.isInitialized){
       lateinitVar = "initialized"
    }
  }
}

ローカル変数では使えないので注意っぽい。lateinitは宣言時には値は決まらないけどあとで必ずnon-nullになるので型宣言はnon-nullで頼む :pray: みたいな機能なので、初期化されてなければ何か処理する、みたいな使い方はあんまり本質的ではない気がする。再代入を防ぐのに使う、というのがありそうではあるが...

インライン関数でデフォルト引数が可能に

今までなかったのか〜。便利。

inline fun <E> Iterable<E>.strings(transform: (E) -> String = { it.toString() }) = 
map { transform(it) }

val defaultStrings = listOf(1, 2, 3).strings()
val customStrings = listOf(1, 2, 3).strings { "($it)" } 

明示的なキャストの情報を使った型推論

Android API Level 26でfindViewByIdfindViewById<T>になったので次のコードは動かなくなったんだけど、明示的キャストを型推論に使うから既存コードいじらなくてもいけるぜ的なやつ。助かる。

// findViewById<T>のTがどこにもないのでエラーになってたけど、
// as ButtonのButtonをTとして認識してくれるようになった。
val button = findViewById(R.id.button) as Button

スマートキャストがより賢く

よくわかってないけど、キャストが成功したと解釈できるときはキャスト対象をその後の文脈でキャスト後の型として扱えるようになったみたいな感じ。複雑な検証もいい感じに拾うぜ的なやつ。

val firstChar = (s as? CharSequence)?.firstOrNull()
if (firstChar != null)
  return s.count { it == firstChar } // s: Any is smart cast to CharSequence

val firstItem = (s as? Iterable<*>)?.firstOrNull()
if (firstItem != null)
  return s.count { it == firstItem } // s: Any is smart cast to Iterable<*>

もうひとつ、クロージャでキャプチャする変数を、クロージャより手前でいじる場合のみ、クロージャ内でスマートキャストが利くようになった。

var x: String? = null
if (flag) x = "Yahoo!"

run {
    if (x != null) {
        println(x.length) // x is smart cast to String
    }
}

明示的にこれによさそうみたいなコード例が思いつかないけど多分どっかしら恩恵を受けそうな気はする。

Bound Callable Referenceの省略表記

クラスの関数やプロパティの参照は次のように書く。

val showMessage = Hoge::showMessage
val finish = this::finish

今回、this::::に省略して書けるようになった。

val finish = ::finish

はい。

破壊的変更: sound smart casts after try blocks

https://youtrack.jetbrains.com/issue/KT-17929で報告された不具合で、次のコードはコンパイルが通り実行時にクラッシュする。

var s: String?
s = "Test"
try {
  s = null
} catch (ex: Exception) {}
s.hashCode() // スマートキャストで非null扱いになる

1.2.0ではこのコードのコンパイルは通らなくなる。プロダクトで1.2.0にしてもエラーにならなかったのでこういうコードってほぼ書かないよな普通と思いつつ安全になってよかった。

非推奨: スーパークラスのcopy関数をオーバーライドするdata classes

そもそもdata classがなにかを継承するケースってあんまりないと思うけど、もしもcopy関数を持ったクラスを継承したdata classを作ると腐ってしまうので非推奨になった。

例えば次のような感じ。

open class A {
  fun copy(): A {
    return A()
  }
}
data class B(val a: Int) : A()

次のコードの変数bはBクラスが返って欲しいのだけど、実際にはAが返ってしまう。

val b = B(1).copy()
println(b.javaClass.simpleName) // A

data classで継承したいケースがほとんどない気がするのとさらにスーパークラスがcopy関数を持ってるケースってほとんどなさそうだけど、コンパイラがエラーにしてくれると安心ではある。1.2.0では警告で、1.3.0からコンパイルエラーになるとのこと。

非推奨: enumクラスでのネストしたクラスの宣言

次のコードで、A.Xにアクセスすると死ぬ。

enum class A {
    X {
        val x = 1
        class Nested {
            val y = x // It's allowed and seems to be fine, because a Nested instance can use A.X
        }

        val z = Nested() // There wasn't PUTSTATIC A.X yet, fails with NPE
    }
}

これはXのコンストラクタでval zを初期化するときにNestedのコンストラクタでX.xにアクセスするから。nest classはデフォルトでは静的なクラスになるので、まだコンストラクタが走りきってないXに対してアクセスして死んでいる。inner classで宣言するとX.this.xでアクセスしにいくので死なない。1.2.0では警告で、1.3.0からコンパイルエラーになるとのこと。

こんなコード書かねーよ〜〜〜〜とは思う。

非推奨: 名前付き引数で、varargを単一の値で渡す

次のような関数があったとする。

fun print(vararg values: Int) {
  values.forEach {
    println(it)
  }
}

次のような呼び出し方に警告が出るようになった。1.3.0でエラー。アノテーションの配列リテラルのスタイルとの一貫性のためらしい。

print(values = 5)  // warning

次のように書くようになる。

print(values = *intArrayOf(5))

あんまり感想がない。

Deprecation: Throwableを継承するジェネリック型の内部クラス

なんか型安全じゃなくなるらしい。よくわからないけど大変そう。こちらも1.2.0警告、1.3.0エラー。

Deprecation: valプロパティでバッキングフィールドを更新する

こういうのはダメよと。

val a: Int = 0
  get() {
    field++
    return field
  }

valなのに値変わっとるやんけ!と。1.2.0警告、1.3.0エラー。

まとめ

コルーチンのexperimental外れるのまだかな。