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

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

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

14c9795d267f5b85abb98ca5e8780646?s=128

Taro Nagasawa

October 04, 2017
Tweet

Transcript

  1. データクラスの話 スコープ関数の話 R.kt #1 2017-10-04 長澤太郎 @ngsw_taro

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

    エムスリーは 国内最大規模の 医療情報プラット フォームを開発・ 運営しています。 エンジニア募 集中!
  3. 祝☆新刊 10/6発売

  4. もくじ 1. データクラスの基本のおさらい 2. データクラスの制限 3. スコープ関数runとlet 4. スコープ関数applyとalso

  5. 1. データクラスの基本のおさらい

  6. データを表現するクラス class Name(val value: String) val taro = Name("太郎") taro.toString()

    //=> Name@1f32e575 taro == Name("太郎") //=> false
  7. データを表現するクラス class Name(val value: String) val taro = Name("太郎") taro.toString()

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

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

    = Name("太郎") taro.toString() //=> Name(value='太郎') taro == Name("太郎") //=> true 欲しかったやつ!
  10. ちゃんと実装してやればよいのです! 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')" }
  11. ちゃんと実装してやればよいのです! 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')" } ・・・
  12. データクラスが自動で提供してくれる data class Name(val value: String) val taro = Name("太郎")

    taro.toString() //=> Name(value='太郎') taro == Name("太郎") //=> true
  13. イミュータブルに嬉しい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
  14. イミュータブルに嬉しい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 名前付き引数で、必要なものだけ変更する
  15. イミュータブルに嬉しい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だけ変更されている
  16. イミュータブルに嬉しい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 元のインスタンスとは別物
  17. 地味に便利なcomponentNメソッド data class FullName(val firstName: String, val lastName: String) val

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

    nagasawa = FullName("太郎", "長澤") val (first, last) = nagasawa first //=> 太郎 last //=> 長澤 分解宣言
  19. 地味に便利な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 //=> 長澤 データクラスがこのようなメソッド実装を自動生成する
  20. 2. データクラスの制限

  21. [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
  22. [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を付けるとコンパイルエラーとなっていた
  23. [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)
  24. データクラスを継承するのはNG open data class Name(val value: String) data class NameWithRuby(val

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

    value: String, val ruby: String): Name(value) • 実装の継承はあまりよろしくない • できたとしてcopyやcomponentNにおかし なことが起こるか大きな制限がかかる • all-openコンパイラプラグインで継承可能。 ただしサブクラスはデータクラスにできない。
  26. データクラスを継承したくなったら 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 このどちらか、 または両方で対処
  27. 3. スコープ関数runとlet

  28. スコープ関数 対象オブジェクト 戻り値 run this 指定できる let it 指定できる apply

    this 対象オブジェクト also it 対象オブジェクト with this 指定できる
  29. スコープ関数 対象オブジェクト 戻り値 run this 指定できる let it 指定できる apply

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

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

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

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

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

    val exp: Exp? = readExpression() val sum: Number? = if(exp != null) eval(exp) else null 安全呼び出しが使えないからnullチェックを
  35. 対象オブジェクトを変換する • 主に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を使用する
  36. 対象オブジェクトについて副作用を起こす listAdapter.items.clear() listAdapter.items.addAll(newItems) listAdapter.notifyDataSetChanged()

  37. 対象オブジェクトについて副作用を起こす listAdapter.items.clear() listAdapter.items.addAll(newItems) listAdapter.notifyDataSetChanged() 対象オブジェクトのメソッドを呼び出して、いろいろ設定

  38. 対象オブジェクトについて副作用を起こす listAdapter.items.clear() listAdapter.items.addAll(newItems) listAdapter.notifyDataSetChanged() listAdapter.run { items.clear() items.addAll(newItems) notifyDataSetChanged() }

    runを使用する
  39. 対象オブジェクトについて副作用を起こす listAdapter.items.clear() listAdapter.items.addAll(newItems) listAdapter.notifyDataSetChanged() listAdapter.run { items.clear() items.addAll(newItems) notifyDataSetChanged() }

    runを使用する 今回はrunなので、 このブロックの中で thisの意味が変わる。
  40. 対象オブジェクト thisかitか 対象オブジェクト 戻り値 run this 指定できる let it 指定できる

    apply this 対象オブジェクト also it 対象オブジェクト • itの方を使っておけば無難 → 命名できる、文脈を維持 • thisの方を使った方が楽な場面はある → ただし、ネストは避けること
  41. 4. スコープ関数applyとalso

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

    this 対象オブジェクト also it 対象オブジェクト • 対象オブジェクトの生成に伴う各種設定 • fluent interface, メソッドチェーンの代替?
  43. 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) 【理想】 【現実】
  44. 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
  45. applyでBundleオブジェクト生成から設定 val bundle: Bundle = Bundle().apply { putInt("one", 1) putInt("two",

    2) putInt("three", 3) }
  46. 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でも同じことができるが、自分 自身を返す必要がある。
  47. スコープ関数 選択チャート あるオブジェクトに対して、 まとめて何か操作をした い! 対象オブジェクトへの 参照はNullableである。 戻り値として任意のオブジェ クトを指定したい。 あるいは興味がない。

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

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