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

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

2b3d68169a23966b99cfc481e40e6593?s=47 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

2b3d68169a23966b99cfc481e40e6593?s=128

hayasshi

January 24, 2019
Tweet

Transcript

  1. Scala / re ned で ` 型く` 表明プログラミング 2019-01-24 Scala

    関西勉強会 1
  2. 自己紹介 林 大介 Twitter: @hayasshi_ GitHub: hayasshi Chatwork 株式会社 Scala

    2.9 系から使い始め(2013 年頃) 型好き、Akka 好き 2019-01-24 Scala 関西勉強会 2
  3. 表明プログラミング 2019-01-24 Scala 関西勉強会 3

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

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

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

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

  10. re ned とは fthomas/re ned re nement type = 篩(

    ふるい) 型 型 + 述語 型に述語でとり得る値のみという制限ができる Scala 関西勉強会 - Summit 直前スペシャル にて @rider_yi さんが紹介されており、「これすげー!」と思って 今回とりあげてみました。 re ned で安全なコードを書く 2019-01-24 Scala 関西勉強会 10
  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 <console>:24: error: Predicate failed: (0 > 0). val naturalNumber: Int Refined Positive = 0 ※みやすくしています 2019-01-24 Scala 関西勉強会 11
  12. re ned scala> type NonEmptyString = String Refined NonEmpty scala>

    val nonEmptyString: NonEmptyString = "foobar" nonEmptyString: NonEmptyString = foobar scala> val nonEmptyString: NonEmptyString = "" <console>: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 = " はやし" <console>:28: error: Predicate failed: " はやし".matches("[a-zA-Z0-9]{3,10}"). val nickName: NickNameType = " はやし" 2019-01-24 Scala 関西勉強会 12
  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
  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
  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
  16. re ned のデメリット 2019-01-24 Scala 関西勉強会 16

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

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

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

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

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

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

    2016 re ned で安全なコードを書く fthomas/re ned 2019-01-24 Scala 関西勉強会 24