算譜王におれはなる!!!!

偏りはあると思うけど情報技術全般についてマイペースに書くよ。

KotlinでFragmentのargumentsをいい感じに読み取りたい

夢と魔法の待ち時間というAndroidアプリを趣味で作っていて、これを100% Kotlinに移行するためここ最近ごりごりコードを書います。

1つのフラグメントで両パーク(陸と海)に対応するようにしていて、そんな感じのフラグメントがいくつかあります。 例えばアトラクション一覧のフラグメントとか、グリーティング一覧のフラグメントとか。 どちらのパークかはargumentsで与えられるという寸法です。

class AttractionFragment: Fragment() {
    val park: Park
    get() = arguments.getSerializable("park") as Park

    // 省略
}
class GreetingFragment: Fragment() {
    val park: Park
    get() = arguments.getSerializable("park") as Park

    // 省略
}

だいたいこんな感じでparkの部分が完全に一致。 すっきり記述できる方法はないかなーと考えました(BaseFragmentとかの導入は却下)。

ScalaのSelf Typeみたいなやつ (失敗)

失敗というか既に廃止された言語機能です。 次のコードのように、インタフェースが自分の実装されるクラスを指定できる感じです。 WithParkインタフェースはFragmentを要求しているので、自分の中でFragmentargumentsにアクセスできます。

interface WithPark: Fragment {
    val park: Park
    get() = arguments.getSerializable("park") as Park
}

class AttractionFragment: Fragment(), WithPark {
    fun とあるメソッド() {
        println(park) // パークをargumentsから取れる
    }
}

インタフェースだけで頑張る (成功)

話の順序的に真ん中に成功例を載せます。

argumentsを抽象プロパティとして提供するインタフェースにすれば、それっぽくなります。

interface WithPark {
    fun getArguments(): Bundle

    val park: Park
    get() = getArguments().getSerializable("park") as Park
}

class AttractionFragment: Fragment(), WithPark {
    fun とあるメソッド() {
        println(park) // パークをargumentsから取れる
    }
}

問題があるとすればFragment以外がWithParkになれる点です。 いや、こっちの方がより抽象的でいいのかな。でもスコープというか使用できる範囲は狭いほうがいいような。。

拡張プロパティで頑張る (失敗)

最後にまた失敗例です。

今回はWithParkインタフェースは、拡張プロパティのための印です。

interface WithPark

val <T> T.park: String where T: Fragment, T: WithPark
get() = argument.getString("park") as Park

class AttractionFragment: Fragment(), WithPark {
    fun とあるメソッド() {
        println(park) // なぜかここでコンパイルエラー
    }
}

拡張プロパティparkは、FragmentかつWithParkな型をレシーバに取ります。 これにより最初に挙げた例のSelf Typeのようなことができます。 WithParkは誰でも実装できますが、それがFragment以外だとparkプロパティを呼び出せません。

しかし何故か呼び出し箇所でコンパイルエラーになってしまいます。 数日前に同じ問題に直面した人がチケット上げてました。

https://youtrack.jetbrains.com/issue/KT-9630

(僕の中での)結論

3番目の方法ができるのを待って、それまでは2番目の方法で凌ぐ。

第2回関西Kotlin勉強会で発表してきたよ〜 #ashiyakt

f:id:ngsw_taro:20150924221632j:plain

9/19に関西Kotlin勉強会に参加すべく兵庫は芦屋へ行ってきました。

connpass.com

私の発表資料はこちら↓

前回同様いい意味でゆる〜い雰囲気で、発表後の質疑応答も活発だったし、余った時間であーだこーだ議論するのも楽しかったです。

@takuji31さんの飛び入りLTもありました。

ちなみに冒頭の写真は@yy_yankさんの発表資料です。

懇親会も盛り上がりました!

9/20にはみんなでセルフ祭に行きました。 関西Kotlin勉強会に行くついでにセルフ祭、定番になりそう。

Kotlin M13で追加されたsealed class

Kotlin マイルストーン13 がリリースされました!

blog.jetbrains.com

lateinit修飾子については↓の記事をば。

Kotlin M13で追加されたlateinit試してみた - 算譜王におれはなる!!!!

sealed class

クラスに付けられるsealed修飾子が追加されました。 一言で言うと、クラスの継承を制限するための修飾子です。 sealedが付いたクラスを継承するにはある条件を満たす必要があるということです。 そういう意味でScalasealedと似ていてC#のそれとは異なります。

肝心のsealed classを継承するための条件ですが、現時点ではsealed classにネストされたクラスであることです。

sealed class A {
  class B: A()
}

class C: A() // これはダメ

そのうち、この制限を緩和して同一ソースファイル内での継承も許可するそうです。

さて、このsealed classですが何の役に立つのかと言うと、リリースノート内でAlgebraic Data Types(代数的データ型)というキーワードが登場しています。 例えば列挙型をenum classではなくsealed classとそのサブクラスで表現可能です。

sealed class Color {
  object Red:   Color()
  object Green: Color()
  object Blue:  Color()
}

val name = when(color) {
  Color.Red   -> "赤"
  Color.Green -> "緑"
  Color.Blue  -> "青"
}

when式の分岐でelseがないことに注目してください。 Colorsealed classとして定義されており、他の場所でそのサブクラスが定義されないことを保証します。 そのためwhen式の分岐でRedGreenBlueすべての場合が考慮されていることをコンパイラは知っているのでelseが不要になります。 Coloropen classとして定義された場合には、他の場所でもそのサブクラスが定義される可能性があるのでコンパイラはwhen式にelseを要求します。

次に、sealed classを使って直和型を表現してみます。

import Option.*

sealed class Option<out T> {
  class Some<T>(val value: T): Option<T>()
  object None: Option<Nothing>()
}

val name = when (userOption) {
  is Some<User> -> userOption.value.name
  is None       -> "null"
}

便利ですね〜。 ちなみにインタフェースにはsealedを付けられないみたいです。

Kotlin M13で追加されたlateinit試してみた

Kotlin マイルストーン13 がリリースされました!

blog.jetbrains.com

その中で追加されたlateinitというプロパティにつける修飾子が便利そう。 プロパティの初期化を先延ばしにできるので既存のフレームワークにインジェクトしてもらえるって寸法です。

「プロパティの初期化の先延ばし」っていうとDelegated propertyを思い出すけど、これには問題があったのでした。。

ということでlateinitを試してみました。

import com.google.inject.Guice
import javax.inject.Inject
import kotlin.properties.Delegates

class Greeter {
  fun greet() {
    println("Hello")
  }
}

class Client {
  @Inject
  lateinit val greeter1: Greeter

  @Inject
  val greeter2: Greeter by Delegates.notNull()
}

fun main(args: Array<String>) {
  val injector = Guice.createInjector()
  val client = injector.getInstance(Client::class.java)
  client.greeter1.greet() //=> Hello
  client.greeter2.greet() // IllegalStateException thrown
}

Google Guiceでインジェクトしています。 lateinitが付いているgreeter1は見事インジェクトされています!しかし、Delegates.notNull()をつけているgreeter2はインジェクトされておらずgreet()呼び出し時に初期化が済んでないよと例外が投げられてしまいました。

そのほかの変更点についてはまた後ほど。

ちなみにこれならいける

var greeter2: Greeter by Delegates.notNull()
  @Inject set