Upgrade to Pro — share decks privately, control downloads, hide ads and more …

データクラスの話/スコープ関数の話 #rkt

データクラスの話/スコープ関数の話 #rkt

Taro Nagasawa

October 04, 2017
Tweet

More Decks by Taro Nagasawa

Other Decks in Programming

Transcript

  1. 自己紹介 • 長澤 太郎(たろーって呼んでね) • @ngsw_taro • エムスリー株式会社 • ディズニーが大好き!

    エムスリーは 国内最大規模の 医療情報プラット フォームを開発・ 運営しています。 エンジニア募 集中!
  2. データを表現するクラス class Name(val value: String) val taro = Name("太郎") taro.toString()

    //=> Name@1f32e575 taro == Name("太郎") //=> false 同じ名前で、インスタンスを区 別する必要がないからtrueを 期待する 役に立ちそうにない 情報
  3. データを表現するクラス class Name(val value: String) { ??? } val taro

    = Name("太郎") taro.toString() //=> Name(value='太郎') taro == Name("太郎") //=> true 欲しかったやつ!
  4. データを表現するクラス class Name(val value: String) { ??? } val taro

    = Name("太郎") taro.toString() //=> Name(value='太郎') taro == Name("太郎") //=> true 欲しかったやつ!
  5. ちゃんと実装してやればよいのです! class Name(val value: String) { override fun equals(other: Any?):

    Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false other as Name if (value != other.value) return false return true } override fun hashCode(): Int = value.hashCode() override fun toString(): String = "Name(value='$value')" }
  6. ちゃんと実装してやればよいのです! class Name(val value: String) { override fun equals(other: Any?):

    Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false other as Name if (value != other.value) return false return true } override fun hashCode(): Int = value.hashCode() override fun toString(): String = "Name(value='$value')" } ・・・
  7. データクラスが自動で提供してくれる data class Name(val value: String) val taro = Name("太郎")

    taro.toString() //=> Name(value='太郎') taro == Name("太郎") //=> true
  8. イミュータブルに嬉しいcopyメソッド data class FullName(val firstName: String, val lastName: String) val

    nagasawa = FullName("太郎", "長澤") val urashima = nagasawa.copy(lastName = "浦島") urashima.firstName //=> 太郎 urashima.lastName //=> 浦島 nagasawa === urashima //=> false
  9. イミュータブルに嬉しいcopyメソッド data class FullName(val firstName: String, val lastName: String) val

    nagasawa = FullName("太郎", "長澤") val urashima = nagasawa.copy(lastName = "浦島") urashima.firstName //=> 太郎 urashima.lastName //=> 浦島 nagasawa === urashima //=> false 名前付き引数で、必要なものだけ変更する
  10. イミュータブルに嬉しいcopyメソッド data class FullName(val firstName: String, val lastName: String) val

    nagasawa = FullName("太郎", "長澤") val urashima = nagasawa.copy(lastName = "浦島") urashima.firstName //=> 太郎 urashima.lastName //=> 浦島 nagasawa === urashima //=> false lastNameだけ変更されている
  11. イミュータブルに嬉しいcopyメソッド data class FullName(val firstName: String, val lastName: String) val

    nagasawa = FullName("太郎", "長澤") val urashima = nagasawa.copy(lastName = "浦島") urashima.firstName //=> 太郎 urashima.lastName //=> 浦島 nagasawa === urashima //=> false 元のインスタンスとは別物
  12. 地味に便利なcomponentNメソッド data class FullName(val firstName: String, val lastName: String) val

    nagasawa = FullName("太郎", "長澤") val (first, last) = nagasawa first //=> 太郎 last //=> 長澤
  13. 地味に便利なcomponentNメソッド data class FullName(val firstName: String, val lastName: String) val

    nagasawa = FullName("太郎", "長澤") val (first, last) = nagasawa first //=> 太郎 last //=> 長澤 分解宣言
  14. 地味に便利なcomponentNメソッド data class FullName(val firstName: String, val lastName: String) {

    operator fun component1(): String = firstName operator fun component2(): String = lastName } val nagasawa = FullName("太郎", "長澤") val (first, last) = nagasawa first //=> 太郎 last //=> 長澤 データクラスがこのようなメソッド実装を自動生成する
  15. [ver1.1より前] クラスを継承できなかった sealed class Exp { class Number(val value: Int):

    Exp() class Addition(val x: Exp, val y: Exp): Exp() } val exp: Exp = Addition(Number(1), Number(2)) val sum: Number = eval(exp) sum //=> Exp$Number@2ff4acd0
  16. [ver1.1より前] クラスを継承できなかった sealed class Exp { class Number(val value: Int):

    Exp() class Addition(val x: Exp, val y: Exp): Exp() } val exp: Exp = Addition(Number(1), Number(2)) val sum: Number = eval(exp) sum //=> Exp$Number@2ff4acd0 dataを付けるとコンパイルエラーとなっていた
  17. [ver1.1以降] クラスの継承OK sealed class Exp { data class Number(val value:

    Int): Exp() data class Addition(val x: Exp, val y: Exp): Exp() } val exp: Exp = Addition(Number(1), Number(2)) val sum: Number = eval(exp) sum //=> Number(value=3)
  18. データクラスを継承するのはNG open data class Name(val value: String) data class NameWithRuby(val

    value: String, val ruby: String): Name(value) 同時にマークすることはできない
  19. データクラスを継承するのはNG open data class Name(val value: String) data class NameWithRuby(val

    value: String, val ruby: String): Name(value) • 実装の継承はあまりよろしくない • できたとしてcopyやcomponentNにおかし なことが起こるか大きな制限がかかる • all-openコンパイラプラグインで継承可能。 ただしサブクラスはデータクラスにできない。
  20. データクラスを継承したくなったら data class Name(val value: String) data class NameWithRuby(val value:

    String, val ruby: String) { fun toName(): Name = Name(value) } interface StringWrapper { val value: String fun component1(): String } data class Name(override val value: String): StringWrapper data class NameWithRuby(override val value: String, val ruby: String): StringWrapper このどちらか、 または両方で対処
  21. スコープ関数 対象オブジェクト 戻り値 run this 指定できる let it 指定できる apply

    this 対象オブジェクト also it 対象オブジェクト with this 指定できる
  22. runとlet 対象オブジェクト 戻り値 run this 指定できる let it 指定できる apply

    this 対象オブジェクト also it 対象オブジェクト • 対象オブジェクトを変換する • 対象オブジェクトについて副作用を起こす
  23. 対象オブジェクトを変換する • 主にNullableな参照に対して使用される fun eval(exp: Exp): Number { ... }

    val exp: Exp? = readExpression() val sum: Number? = if(exp != null) eval(exp) else null
  24. 対象オブジェクトを変換する • 主にNullableな参照に対して使用される fun eval(exp: Exp): Number { ... }

    val exp: Exp? = readExpression() val sum: Number? = if(exp != null) eval(exp) else null NotNullを取る関数
  25. 対象オブジェクトを変換する • 主にNullableな参照に対して使用される fun eval(exp: Exp): Number { ... }

    val exp: Exp? = readExpression() val sum: Number? = if(exp != null) eval(exp) else null Nullableな変数
  26. 対象オブジェクトを変換する • 主にNullableな参照に対して使用される fun eval(exp: Exp): Number { ... }

    val exp: Exp? = readExpression() val sum: Number? = if(exp != null) eval(exp) else null 安全呼び出しが使えないからnullチェックを
  27. 対象オブジェクトを変換する • 主にNullableな参照に対して使用される fun eval(exp: Exp): Number { ... }

    val exp: Exp? = readExpression() val sum: Number? = if(exp != null) eval(exp) else null val sum: Number? = exp?.let { eval(it) } letを使用する
  28. 対象オブジェクト thisかitか 対象オブジェクト 戻り値 run this 指定できる let it 指定できる

    apply this 対象オブジェクト also it 対象オブジェクト • itの方を使っておけば無難 → 命名できる、文脈を維持 • thisの方を使った方が楽な場面はある → ただし、ネストは避けること
  29. applyとalso 対象オブジェクト 戻り値 run this 指定できる let it 指定できる apply

    this 対象オブジェクト also it 対象オブジェクト • 対象オブジェクトの生成に伴う各種設定 • fluent interface, メソッドチェーンの代替?
  30. Android, Bundleクラスの理想と現実 val bundle: Bundle = Bundle() .putInt("one", 1) .putInt("two",

    2) .putInt("three", 3) val bundle: Bundle = Bundle() bundle.putInt("one", 1) bundle.putInt("two", 2) bundle.putInt("three", 3) 【理想】 【現実】
  31. Android, Bundleクラスの理想と現実 val bundle: Bundle = Bundle() .putInt("one", 1) .putInt("two",

    2) .putInt("three", 3) val bundle: Bundle = Bundle() bundle.putInt("one", 1) bundle.putInt("two", 2) bundle.putInt("three", 3) 【理想】 【現実】 そこでapply
  32. applyでBundleオブジェクト生成から設定 val bundle: Bundle = Bundle().run { putInt("one", 1) putInt("two",

    2) putInt("three", 3) this } val bundle: Bundle = Bundle().apply { putInt("one", 1) putInt("two", 2) putInt("three", 3) } runでも同じことができるが、自分 自身を返す必要がある。
  33. スコープ関数 選択チャート あるオブジェクトに対して、 まとめて何か操作をした い! 対象オブジェクトへの 参照はNullableである。 戻り値として任意のオブジェ クトを指定したい。 あるいは興味がない。

    thisの文脈が複雑である (内部クラスやレシーバ付き ラムダなど) 名前を付けたい。 let apply run also with 使う場面では ないかも? 人とは違うことをした い! 名前を付けたい。 thisの文脈が複雑である (内部クラスやレシーバ付き ラムダなど) アナタに合う スコープ関数が きっと見つかる! YES NO
  34. まとめ • データクラスにすると便利なメソッドが無料で手に入る • [ver1.1] サブクラスをデータクラスにできるようになった • データクラスをスーパクラスにはできないし、よいアイデアでは ない •

    スコープ関数は、対象オブジェクトの見え方と戻り値によって 分類することができる • runとletは変換、副作用に • applyとalsoはオブジェクトの生成と設定を一括で