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

Scala / refined で`型く`表明プログラミング / Programing with type level assertion

hayasshi
January 24, 2019

Scala / refined で`型く`表明プログラミング / Programing with type level assertion

## 参考情報

PHP7 で堅牢なコードを書く - 例外処理、表明プログラミング、契約による設計 / PHP Conference 2016
https://speakerdeck.com/twada/php-conference-2016?slide=68

refinedで安全なコードを書く
https://rider-yi.github.io/2018-10-24-refined/

fthomas/refined
https://github.com/fthomas/refined

## 簡易ベンチマークコード

https://github.com/hayasshi/refined-sample

hayasshi

January 24, 2019
Tweet

More Decks by hayasshi

Other Decks in Programming

Transcript

  1. Scala / re ned

    `
    型く`
    表明プログラミング
    2019-01-24 Scala
    関西勉強会 1

    View full-size slide

  2. 自己紹介
    林 大介
    Twitter: @hayasshi_
    GitHub: hayasshi
    Chatwork
    株式会社
    Scala 2.9
    系から使い始め(2013
    年頃)
    型好き、Akka
    好き
    2019-01-24 Scala
    関西勉強会 2

    View full-size slide

  3. 表明プログラミング
    2019-01-24 Scala
    関西勉強会 3

    View full-size slide

  4. 表明プログラミングとは
    入力や結果などをチェックして想定外の値の場合には弾くことで
    全体の動作を正常に保つ
    防御的プログラミングのやり方の一つ
    2019-01-26
    追記
    ご指摘をいただき、現時点で表記されていることが正しい確証がありま
    せんので、取り消します。
    以降「表明プログラミング」の部分は「事前条件を表明する」という形
    で適宜読み替えていただければと思います。
    assert
    メソッドをつかって表明する
    2019-01-24 Scala
    関西勉強会 4

    View full-size slide

  5. Scala
    での表明プログラミング
    val namePattern = "[A-Z][a-zA-Z0-9]{0,9}".r.pattern
    case class User(id: Long, name: String, age: Int) {
    assert(id > 0)
    assert(namePattern.matcher(name).matches())
    assert(age >= 18)
    def changeName(name: String): User = {
    assert(namePattern.matcher(name).matches())
    this.copy(name = name)
    }
    }
    2019-01-24 Scala
    関西勉強会 5

    View full-size slide

  6. Scala
    での表明プログラミング
    val namePattern = "[A-Z][a-zA-Z0-9]{0,9}".r.pattern
    case class UserId(value: Long) {
    assert(value > 0)
    }
    case class UserName(value: String) {
    assert(namePattern.matcher(value).matches())
    }
    case class UserAge(value: Int) {
    assert(value >= 18)
    }
    case class User(id: UserId, name: UserName, age: UserAge) {
    def changeName(name: UserName): User = {
    this.copy(name = name)
    }
    }
    2019-01-24 Scala
    関西勉強会 6

    View full-size slide

  7. 表明プログラミングのメリット
    fail fast
    想定外の入力があれば早期にエラーをあげることで
    プログラム全体の動作を保護する
    documentation
    表明のコードで何を求めているかが明示され
    読み手が理解しやすくなる(
    コミュニケーションを促進する)
    2019-01-24 Scala
    関西勉強会 7

    View full-size slide

  8. 実行時に検知かぁ
    制約を課してコンパイル時に検知できればなぁ
    2019-01-24 Scala
    関西勉強会 8

    View full-size slide

  9. re ned
    2019-01-24 Scala
    関西勉強会 9

    View full-size slide

  10. re ned
    とは
    fthomas/re ned
    re nement type =
    篩(
    ふるい)

    型 +
    述語
    型に述語でとり得る値のみという制限ができる
    Scala
    関西勉強会 - Summit
    直前スペシャル にて
    @rider_yi
    さんが紹介されており、「これすげー!」と思って
    今回とりあげてみました。
    re ned
    で安全なコードを書く
    2019-01-24 Scala
    関西勉強会 10

    View full-size slide

  11. re ned
    import eu.timepit.refined._
    import eu.timepit.refined.api.Refined
    import eu.timepit.refined.numeric._
    import eu.timepit.refined.string._
    import eu.timepit.refined.collection._
    import eu.timepit.refined.auto._
    scala> val naturalNumber: Int Refined Positive = 1
    naturalNumber: Refined[Int,Positive] = 1
    scala> val naturalNumber: Int Refined Positive = 0
    :24: error: Predicate failed: (0 > 0).
    val naturalNumber: Int Refined Positive = 0
    ※みやすくしています
    2019-01-24 Scala
    関西勉強会 11

    View full-size slide

  12. re ned
    scala> type NonEmptyString = String Refined NonEmpty
    scala> val nonEmptyString: NonEmptyString = "foobar"
    nonEmptyString: NonEmptyString = foobar
    scala> val nonEmptyString: NonEmptyString = ""
    :28: error: Predicate isEmpty() did not fail.
    val nonEmptyString: NonEmptyString = ""
    scala> type NickNameType =
    String Refined MatchesRegex[W.`"[a-zA-Z0-9]{3,10}"`.T]
    scala> val nickName: NickNameType = "hayasshi"
    nickName: NickNameType = hayasshi
    scala> val nickName: NickNameType = "
    はやし"
    :28: error: Predicate failed: "
    はやし".matches("[a-zA-Z0-9]{3,10}").
    val nickName: NickNameType = "
    はやし"
    2019-01-24 Scala
    関西勉強会 12

    View full-size slide

  13. re ned
    ※みやすくしています
    scala> val x = "hayasshi"
    x: String = hayasshi
    scala> refineV[MatchesRegex[W.`"[a-zA-Z0-9]{3,10}"`.T]](x)
    res0: Either[String,Refined[String,MatchesRegex[String("[a-zA-Z0-9]{3,10}")]]]
    = Right(hayasshi)
    scala> val x = "
    はやし"
    x: String =
    はやし
    scala> refineV[MatchesRegex[W.`"[a-zA-Z0-9]{3,10}"`.T]](x)
    res1: Either[String,Refined[String,MatchesRegex[String("[a-zA-Z0-9]{3,10}")]]]
    = Left(Predicate failed: "
    はやし".matches("[a-zA-Z0-9]{3,10}").)
    2019-01-24 Scala
    関西勉強会 13

    View full-size slide

  14. re ned
    で表明してみる
    case class UserId(value: UserIdType)
    case class UserName(value: UserNameType)
    case class UserAge(value: UserAgeType)
    case class User(id: UserId, name: UserName, age: UserAge) {
    def changeName(name: UserName): User = {
    this.copy(name = name)
    }
    }
    assert
    がなくなりコードがスッキリ!
    type UserIdType = Long Refined Positive
    type UserNameType = String Refined MatchesRegex[W.`"[A-Z][a-zA-Z0-9]{0,9}"`.T]
    type UserAgeType = Int Refined GreaterEqual[W.`18`.T]
    2019-01-24 Scala
    関西勉強会 14

    View full-size slide

  15. re ned
    で表明プログラミングするメリット
    表現可能な範囲で assert
    を使わなくて良いためコードがスリムに
    シグネチャレベルで引き続きドキュメンテーションの効果がある
    変数を渡す場合は refineV[Predicate].apply
    を利用して
    Either[String, T Refined Predicate]
    を取得しチェックを強制する
    各ライブラリとの統合モジュールも様々なカテゴリである
    re ned#external-modules
    Json
    エンコード/
    デコード、DB
    アクセスライブラリなど
    自分で各フィールドに対して refineV
    せずとも Either[E, T]
    など
    で取得できる
    2019-01-24 Scala
    関西勉強会 15

    View full-size slide

  16. re ned
    のデメリット
    2019-01-24 Scala
    関西勉強会 16

    View full-size slide

  17. コンパイルが遅くなる
    re ned
    はマクロや implicit
    を多く使っているため
    コンパイルが長くなる
    今後の Scala
    自体の進化でどうにかなる可能性も?
    2019-01-24 Scala
    関西勉強会 17

    View full-size slide

  18. 実行速度
    リテラル
    コンパイル時にすべて解決され実行時にはリテラルを扱うのと同じ形と
    なるため速度劣化がない
    2019-01-24 Scala
    関西勉強会 18

    View full-size slide

  19. 実行速度
    変数
    sbt-jmh
    をつかって簡易ベンチマークを実施 (
    コード)
    2019-01-24 Scala
    関西勉強会 19

    View full-size slide

  20. 実行速度
    変数
    sbt-jmh
    をつかって簡易ベンチマークを実施 (
    コード)
    sbt clean "jmh:run -i 10 -wi 10 -f1 -t1"
    [info] Benchmark Mode Cnt Score Error Units
    [info] Main.runCreateAssertObjects thrpt 10 867.783 ± 6.132 ops/s
    [info] Main.runCreateRefinedObjects thrpt 10 229.468 ± 9.618 ops/s
    4
    倍くらい遅い
    2019-01-24 Scala
    関西勉強会 20

    View full-size slide

  21. 実行速度
    Web
    アプリケーションという文脈では、リクエストや他のサービス、
    DB
    にアクセスすることがほとんどで、
    基本的には 変数 を扱うことになる
    オンライン処理で使うのは厳しいかもしれない...
    2019-01-24 Scala
    関西勉強会 21

    View full-size slide

  22. re ned
    の活用を考えてみた(
    提案)
    最近コンテナ全盛で yaml
    を書くことが多い
    json <=> yaml
    は変換できるので Scala
    で json
    生成するツールなどで
    各フィールドの制約につかえないか
    case class KubernetesManifest(
    kind: ResourceType.Pod,
    metadataName: String Refined NonEmpty Refined MaxSize[W.`24`.T],
    ...
    clusterIp: String Refined IPv4,
    )
    2019-01-24 Scala
    関西勉強会 22

    View full-size slide

  23. まとめ
    re ned
    を使うことで型をつかって表明プログラミングできる
    re ned
    の実行は遅そうに見える...
    実行速度がそこまで求められない何かに使おう
    Scala
    言語自体含め今後の進化に期待
    2019-01-24 Scala
    関西勉強会 23

    View full-size slide

  24. 参考/
    引用
    PHP7
    で堅牢なコードを書く -
    例外処理、表明プログラミング、契約
    による設計 / PHP Conference 2016
    re ned
    で安全なコードを書く
    fthomas/re ned
    2019-01-24 Scala
    関西勉強会 24

    View full-size slide