visible true

技術的なメモを書く

Kotlin学習の知見のかたまり「Kotlinスタートブック」はサイコーのKotlin入門本です

Kotlinスタートブックを献本頂いたので感想を書きます。

Kotlinスタートブック-新しいAndroidプログラミング

概要

KotlinスタートブックはJavaエンジニアおよびAndroidアプリケーションエンジニア向けにKotlinの基本を解説する本です。Kotlinの文法や機能を広範囲にわたって身に付けられます。また後半にはAndroidアプリケーションへKotlinを適用する例を解説する章があり、Androidアプリケーションにどのように導入するかイメージをつかむことができるでしょう。

こんな人におすすめ

  • Kotlinを効率的に学習したいひと
  • Android Javaに疲れたひと
  • Android Javaはやりたくないひと
  • AndroidはまぁJava 8でいいじゃん、と確認したいひと

解説

Kotlinについて「聞いたことがある」とか「ちょっと触ったことがある」とかいった人にとても良い入門本だと思います。また1.0未満のときに触って捨てた人にも良いです。

Kotlin学習の知見のかたまり

Kotlinのドキュメントや情報はそれなりに充実してきているものの、どこから手をつけるか、どのように活用するかについてはKotlinを触る人それぞれに依存していました。 Kotlinスタートブックは日本Kotlinユーザグループ代表の@ngsw_taroさん(黙認エバンジェリスト)が、Kotlinに出会ってから現在に至るまでの約3年間のあいだに学んだ事をしっかりと消化したうえで、これからKotlinを学ぶ人へ向けて適切な学習の順序付けを行った本です。3,780円でKotlinを学習するために必要な時間を大幅に節約できるのでたいへんお得です。

Androidに適用するアイデアがたくさん

Androidは開発に占める言語の割合が小さいので、Kotlinに対しては腰が重い人が多いのではないかと思います。 KotlinスタートブックにはKotlinの基本を学んだあとAndroidアプリケーションをひとつ作る章があります。 アプリケーション自体はシンプルであるものの、そこかしこにKotlinのフレーバーが散りばめられており、Kotlinを学んだあとに読めばきっと「なるほどこういう風に使えばいいのか最高だな」と思うに違いありません。 さらにそこから「では、こういったことは出来るのだろうか?」という問いが立ち上がり、芋づる式にさまざまなAndroidにまつわる課題の解法を思い描いていけるのではないかと思います。

感想

個人的には「本を書く時あるある」を感じながら楽しんで読めました。「あ〜ここ、こういう風に書き出したくなるよなー」「ここは指摘があって色々見直した跡があるな」「説明めんどくさすぎてこれ省略したな」「ここはめちゃくちゃ攻めたな」とかいった箇所がたくさんあって面白いと共に胃がキュッとなりました。

Kotlinを実際にAndroidアプリケーション開発に導入する気がなくても、新しい言語を学ぶという側面で読む価値があると思うのでぜひ皆さんお手にとってお召し上がりください。

KotlinのDestructuring Declarationsを非構造化宣言呼ぶことにした けど色々あって分解宣言にした〜

分割代入とか分解宣言とか呼んだりするケースを見かけるけどどうもしっくりこないので非構造化宣言と呼ぶことにした。

  • Destructuring = 非構造化
  • Declarations = 宣言

じっさいデータ構造を複数の変数へ非構造化してるしー。まぁそれなら分解でもいいじゃんて気もするけどDestructuringは分解じゃないっぽいしー

val (id, name) = Pair(1, "mike")

まーそんなにこだわりはないけどー。

2016/6/6 追記

こういう感じがいいのではというアドバイスを元に「分解宣言」と呼ぶことにする事にしました。ありがとうございます。

中略

AndroidでJava8環境 2016

RxAndroidとRetrolambdaで大体Java8をAndroidに持ち込む - visible trueから1年以上経過して界隈も色々更新されていってます。ということでイマドキのJava8環境構築をメモします。

バックポートライブラリとJava8の機能

バックポートライブラリとそのライブラリがカバーするJava8の機能を列挙します

実現不可の機能

次の機能はバックポートライブラリ等では実現不可能です*1

  • interfaceのデフォルト実装
  • interfaceのstaticメソッド

build.gradle

次の設定はRetrolambdaを使っています。そのまんまコピペでは使えないと思うので適切な場所にコピーしてください。

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
      classpath 'me.tatarka:gradle-retrolambda:3.2.5'
    }
}


apply plugin: 'me.tatarka.retrolambda'

dependencies {
  compile 'com.annimon:stream:1.0.9'
  compile 'com.jakewharton.threetenabp:threetenabp:1.0.3'
}

まとめ

結構イケるねJava8。

*1:一応minSdkVersionを24にすればいけます

Kotlin Tips: Reified Type Parametersで型チェックを汎用化する

問題

Kotlin Tips : Intentに必要な値が入っていない場合例外をスローしたい - visible trueではgetLongOrThrow()をIntentに追加しlazyを使う事で可読性を高めた。

Intent.getIntOrThrow(key: String)のおさらい

fun Intent.getIntOrThrow(key: String): Int =
  this.extras.get(key).let {
    if (it !is Int) {
      throw IllegalArgumentException("Extras don't have Int value specified by key $key")
    }
    return it
  }

さて、ではStringならどうか。当然次のようなコードを追加すれば実現できる。

fun Intent.getStringOrThrow(key: String): String =
  this.extras.get(key).let {
    if (it !is String) {
      throw IllegalArgumentException("Extras don't have String value specified by key $key")
    }
    return it
  }

getIntOrThrow(key: String)とほとんど同じ実装で戻り値と型チェック部分だけが異なる。今回は以下の点にフォーカスする。

  • IntentのgetExtraの型の種類は28個ある。28回実装するのはつらい
  • getStringOrThrowなど機能的な命名を出来るだけ意図を表す命名にしたい

改善策

Reified Type Parametersを使う。Reified Type Parametersは具象化された型パラメータを利用できる機能。これによりロジック中の型チェックをTを使って行える(つまりあたかも型消去が起こらなかったかのように扱える。T::class.javaClassとかもできるよ)。

inline fun <reified T> Intent.getRequired(key: String): T {
  extras?.get(key).let {
    if (it !is T) {
        throw IllegalArgumentException("Extras don't have a value specified by key $key")
    }
    return it
  }
}

さらに利用時には型推論によって以下のように書ける。

val value:String = intent.getRequired(key)

ただしlazyを使う時は型の明示が必要となる。といっても型情報は1度しか出てこないので冗長になることはない。

val value by lazy<String> { intent.getRequired(key) }
// or
val value by lazy { intent.getRequired<String>(key) }

やったね

  • Reified Type Parametersによって複数種類の型の取り出しを汎用化できた
  • 型推論のために型はどこかに必ず宣言されるのでメソッド名に型名を含めなくてよくなった
  • Intent.getRequired(key:String)という名前によって意図が明快になった

ついでに

Intent.getOptional()も実装しておく。

inline fun <reified T> Intent.getOptional(name: String, default: T): T {
  extras?.get(name).let {
    if (it == null) {
        return default
    } else {
        return it as T
    }
  }
}

さらなる議論

Reified Type Parametersはinlineだからこそ実現できる機能である。そのためインライン展開のデメリットについては考慮したほうがよさそうだ。といってもバイトコードが膨らむくらいしか思いつかない...。

Kotlin Tips: 可読性のために拡張関数で別名をつける

問題

次のコードはRxJavaのCompletableを利用している例である。

repository.deleteEntry(entry)
    .subscribe(
      { e ->
        view.hideProgress()
        view.showError(e)
      },
      {
        view.hideProgress()
      }
    )

このコードには次の問題がある。

  • repository.deleteEntry()がCompletableを返す事をコードからは読み取れない。
  • subscribe()の第一引数がonError:(Throwable)->UnitだがObservableはonNext:(T)->Unitを第一引数にとるので直感に反する

つまりロジック上問題ないがコードを読んだ時にわかりづらいという問題が発生する。

改善策

2つの問題を解決するためにCompletableにsubscribeCompletable()という名前の関数を追加する。

RxExtensions.kt

fun Completable.subscribeCompletable(onError: (Throwable) -> Unit, onComplete: () -> Unit) = subscribe(onError, onComplete)

Completable.subscribe()に別名をつけることで可読性が向上する。

repository.deleteEntry(entry)
    .subscribeCompletable(
      { e ->
        view.hideProgress()
        view.showError(e)
      },
      {
        view.hideProgress()
      }
    )

さらなる議論

別名をつけると言っても実際は拡張関数を実現するためにRxExtensionsKtクラスが作成され内部にはメソッドやネストしたクラスが作られる。可読性を取るかメソッド数、クラス数を取るかについては別途議論する必要がある。

Kotlin Tips : Intentに必要な値が入っていない場合例外をスローしたい

実際にKotlinを使っているなかで思いついたことなどをこまめにメモると良い気がしたのでなるべく書いていく

Before : デフォルト値を使ってチェックする

以下のようにActivityでintなどのプリミティブな値を受け取るとする。値はrequiredでセットされなかった場合は例外をスローすることで開発者に対して間違った利用方法であることを知らせたい。

class DetailActivity : AppCompatActivity() {
  companion object {
    fun createIntent(context: Context, id: Int): Intent =
           Intent(context, DetailActivity::class.java).apply {
             putExtra("id", id)
           }
  }

  var id: Int = -1

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    id = intent.getIntExtra("id", -1)
    if (id == -1) {
      throw IllegalArgumentException("Should set id. $id")
    }
    // do something
  }
}

このコードには以下の問題点がある

  • プロパティにvarを用い、デフォルト値を設定している
    • プリミティブな型はlateinitが使えない
  • Intent.getIntExtra(key, defaultValue)で必ずデフォルト値が必要なため仕方なく-1を使っている
  • -1の意図が明快ではない
    • プロパティの宣言、Intent.getIntExtra(key, defaultValue)-1を用いているがここからextraにidを設定せずに起動した場合に開発者に間違った利用方法であることを知らせるために例外をスローしたいという意図を読み取るのは難しい。仮にINVALID_ID=1という定数を作ったとしてもvalidationしたいのかどうかを判別するにはIllegalArgumentExceptionのスローの意図を考える必要がある。

After : Intent.getIntOrThrow()を生やしlazyを用いる

まずIntent.getIntExtra(key, defaultValue)を改善する。拡張関数を用いてkeyが存在しない場合に例外をスローする関数を追加する。

extensions.kt

fun Intent.getIntOrThrow(key: String): Int =
      this.extras.get(key).let {
          if (it !is Int) {
            throw IllegalArgumentException("Extras don't have Int value specified by key $key")
          }
          return it
      }

次にプロパティにlazyを用いてIntent.getIntOrThrow()を呼び出す。

class DetailActivity : AppCompatActivity() {
  companion object {
    fun createIntent(context: Context, id: Int): Intent =
           Intent(context, DetailActivity::class.java).apply {
             putExtra("id", id)
           }
  }

  val id: Int by lazy { intent.getIntOrThrow("id") }

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setup(id)
  }
}

これにより以下のことができた。

  • 不必要なデフォルト値を除去できた
  • Intent.getIntOrThrow()によりrequiredな値である事を示せた
  • lazyによりプロパティをvarからvalにできた

さらなる議論

val id: Int by lazy { intent.getIntOrThrow("id") }はbeforeに比べて明らかに意図が明快になったが十分かどうかについてはまだ議論ができそうだ。たとえばより明快にIntent.getRequiredInt(key:String)という名前にしても良いかもしれない。対となるIntent.getOptionalInt(key:String)といった名前の関数を用意するとより意味のあるコードになるかもしれない。

val id: Int by lazy { intent.getRequiredInt("id") }
val name: String? by lazy { intent.getOptionalString("name") }

Kotlin学習とライブラリ作成

第2回Kotlin勉強会 @ Sansan - connpassで「Kotlin学習とライブラリ作成」というタイトルで話してきました。

kmockito

jitpackで配信してるので使えます。

GitHub - sys1yagi/kmockito: Mockito for Kotlin.

allprojects {
  repositories {
    ...
    maven { url "https://jitpack.io" }
  }
}
compile 'com.github.sys1yagi:kmockito:0.1.0'

書き味

大体こんな感じです。assertionのisのバッククォート問題の解決には knit を使ってください。

before

var item = mock(Item::class.java)
`when`(item.length()).thenReturn(10)

assertThat(item.length(), `is`(10))
verify(item).length()

after

var item: Item = mock()
item.length().invoked.thenReturn(10)

assertThat(item.length(), `is`(10))
item.verify().length()

まとめ

大体スライドに書いてありますが、ライブラリ作成をすると実用的なKotlinの検討と学習がいい感じにできるんじゃないかと思います。 100行で大体実用的なものが書けるのであわよくばいい感じの何かをリリースしていい感じになれます。 しかし世界では同じ事考える人がたくさんいるのでデファクトを勝ちとる競争は激しいかもしれません。 とりあえずawesome-kotlinに載れば一人前と言ってよさそうなので頑張りましょう(ぼくはまだ載ってません。載りたい)。 PR出したら大体シュッとマージしてくれるっぽいので自信があるライブラリができたらPRを送るとよいと思います。