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. データクラスの話
    スコープ関数の話
    R.kt #1
    2017-10-04
    長澤太郎 @ngsw_taro

    View Slide

  2. 自己紹介
    ● 長澤 太郎(たろーって呼んでね)
    ● @ngsw_taro
    ● エムスリー株式会社
    ● ディズニーが大好き!
    エムスリーは
    国内最大規模の
    医療情報プラット
    フォームを開発・
    運営しています。
    エンジニア募
    集中!

    View Slide

  3. 祝☆新刊 10/6発売

    View Slide

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

    View Slide

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

    View Slide

  6. データを表現するクラス
    class Name(val value: String)
    val taro = Name("太郎")
    taro.toString() //=> [email protected]
    taro == Name("太郎") //=> false

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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')"
    }

    View Slide

  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')"
    }
    ・・・

    View Slide

  12. データクラスが自動で提供してくれる
    data class Name(val value: String)
    val taro = Name("太郎")
    taro.toString() //=> Name(value='太郎')
    taro == Name("太郎") //=> true

    View Slide

  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

    View Slide

  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
    名前付き引数で、必要なものだけ変更する

    View Slide

  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だけ変更されている

    View Slide

  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
    元のインスタンスとは別物

    View Slide

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

    View Slide

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

    View Slide

  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 //=> 長澤
    データクラスがこのようなメソッド実装を自動生成する

    View Slide

  20. 2. データクラスの制限

    View Slide

  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 //=> [email protected]

    View Slide

  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 //=> [email protected]
    dataを付けるとコンパイルエラーとなっていた

    View Slide

  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)

    View Slide

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

    View Slide

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

    View Slide

  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
    このどちらか、
    または両方で対処

    View Slide

  27. 3. スコープ関数runとlet

    View Slide

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

    View Slide

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

    View Slide

  30. runとlet
    対象オブジェクト 戻り値
    run this 指定できる
    let it 指定できる
    apply this 対象オブジェクト
    also it 対象オブジェクト
    ● 対象オブジェクトを変換する
    ● 対象オブジェクトについて副作用を起こす

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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を使用する

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  39. 対象オブジェクトについて副作用を起こす
    listAdapter.items.clear()
    listAdapter.items.addAll(newItems)
    listAdapter.notifyDataSetChanged()
    listAdapter.run {
    items.clear()
    items.addAll(newItems)
    notifyDataSetChanged()
    }
    runを使用する
    今回はrunなので、
    このブロックの中で
    thisの意味が変わる。

    View Slide

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

    View Slide

  41. 4. スコープ関数applyとalso

    View Slide

  42. applyとalso
    対象オブジェクト 戻り値
    run this 指定できる
    let it 指定できる
    apply this 対象オブジェクト
    also it 対象オブジェクト
    ● 対象オブジェクトの生成に伴う各種設定
    ● fluent interface, メソッドチェーンの代替?

    View Slide

  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)
    【理想】
    【現実】

    View Slide

  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

    View Slide

  45. applyでBundleオブジェクト生成から設定
    val bundle: Bundle = Bundle().apply {
    putInt("one", 1)
    putInt("two", 2)
    putInt("three", 3)
    }

    View Slide

  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でも同じことができるが、自分
    自身を返す必要がある。

    View Slide

  47. スコープ関数 選択チャート
    あるオブジェクトに対して、
    まとめて何か操作をした
    い!
    対象オブジェクトへの
    参照はNullableである。
    戻り値として任意のオブジェ
    クトを指定したい。
    あるいは興味がない。
    thisの文脈が複雑である
    (内部クラスやレシーバ付き
    ラムダなど)
    名前を付けたい。
    let apply
    run
    also
    with
    使う場面では
    ないかも?
    人とは違うことをした
    い!
    名前を付けたい。
    thisの文脈が複雑である
    (内部クラスやレシーバ付き
    ラムダなど)
    アナタに合う
    スコープ関数が
    きっと見つかる!
    YES
    NO

    View Slide

  48. まとめ
    ● データクラスにすると便利なメソッドが無料で手に入る
    ● [ver1.1] サブクラスをデータクラスにできるようになった
    ● データクラスをスーパクラスにはできないし、よいアイデアでは
    ない
    ● スコープ関数は、対象オブジェクトの見え方と戻り値によって
    分類することができる
    ● runとletは変換、副作用に
    ● applyとalsoはオブジェクトの生成と設定を一括で

    View Slide