Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

実行速度 変数 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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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