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. 自己紹介 林 大介 Twitter: @hayasshi_ GitHub: hayasshi Chatwork 株式会社 Scala

    2.9 系から使い始め(2013 年頃) 型好き、Akka 好き 2019-01-24 Scala 関西勉強会 2
  2. 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
  3. 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
  4. re ned とは fthomas/re ned re nement type = 篩(

    ふるい) 型 型 + 述語 型に述語でとり得る値のみという制限ができる Scala 関西勉強会 - Summit 直前スペシャル にて @rider_yi さんが紹介されており、「これすげー!」と思って 今回とりあげてみました。 re ned で安全なコードを書く 2019-01-24 Scala 関西勉強会 10
  5. 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
  6. 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
  7. 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
  8. 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
  9. 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
  10. 実行速度 変数 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
  11. 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
  12. 参考/ 引用 PHP7 で堅牢なコードを書く - 例外処理、表明プログラミング、契約 による設計 / PHP Conference

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