refinedとは
より細かいな型の指定を行うためのscalaのライブラリです。
例えば、scalaは、他言語における UInt や Unsigned Int のような正の値に限定した型指定ができませんが、refinedを用いるとそういったことができるようになります。
Github
導入
build.sbtのライブラリ依存性に以下のように追加します。
libraryDependencies ++= Seq(
"eu.timepit" %% "refined" % "0.9.0",
)
変数定義を行う場合
以下のように通常の変数定義と同じような記述で扱うことができます。
import eu.timepit.refined.api.Refined
import eu.timepit.refined.auto._
import eu.timepit.refined.numeric._
val positive: Int Refined Positive = 5
val negative: Int Refined Positive = -5 // コンパイルエラー
/*
Error:(7, 40) type mismatch;
found : Int(-5)
required: eu.timepit.refined.api.Refined[Int,eu.timepit.refined.numeric.Positive]
(which expands to) eu.timepit.refined.api.Refined[Int,eu.timepit.refined.numeric.Greater[shapeless._0]]
lazy val num: Int Refined Positive = -5
^
*/
上記の場合は、typeでエイリアスを定義しているので、以下のように省略が可能です。
import eu.timepit.refined.auto._
import eu.timepit.refined.types.numeric._
val positive: PosInt = 5
val negative: PosInt = -5 // コンパイルエラー
ただ、変数定義の時にrefinedを用いるよりも、関数定義の時に用いる方がより便利だと思います。
関数定義を行う場合
例えば、メソッドの引数として渡される値を0より大きい値にしたい場合を想定します。
○ refinedを使わない場合
正と負の整数や0が引数として渡ってくるため、その値によってメソッドの内の処理を書き分けなければなりません。
def validate(num: Int): Int = if (num > 0) num else 1
validate(3) // Int = 3
validate(-3) // Int = 1
○ refinedを使う場合
負の整数や0が引数として渡ってきた場合、コンパイル時にエラーとして弾いてくれるため、メソッド内の処理を書き分ける必要がなくなります。
import eu.timepit.refined.auto._
import eu.timepit.refined.types.numeric._
def validate(num: PosInt): Int = num
validate(3) // Int = 3
validate(0) // コンパイルエラー
validate(-3) // コンパイルエラー
/* エラーメッセージ
Error:(16, 12) type mismatch;
found : Int(-3)
required: eu.timepit.refined.types.numeric.PosInt
(which expands to) eu.timepit.refined.api.Refined[Int,eu.timepit.refined.numeric.Greater[shapeless._0]]
validate(-3)
^
*/
このように、引数として渡ってくる値を制限できるため、関数内のロジックが複雑になることを抑制できます。
その他の型指定の例
こちらに列挙されているのを使ってみました。
https://github.com/fthomas/refined#provided-predicates
import eu.timepit.refined._
import eu.timepit.refined.api.Refined
import eu.timepit.refined.auto._
import eu.timepit.refined.types.numeric._
import eu.timepit.refined.numeric._
import eu.timepit.refined.boolean._
import eu.timepit.refined.string._
import eu.timepit.refined.collection._
- 偶数の整数型
val even: Int Refined Even = 10
// コンパイルエラー
//val even: Int Refined Even = 9
- 奇数の整数型
val odd: Int Refined Odd = 9
// コンパイルエラー
//val odd: Int Refined Odd = 10
- nの倍数の整数型
val divisible3: Int Refined Divisible[W.`3`.T] = 9
// コンパイルエラー
//val divisible3: Int Refined Divisible[W.`3`.T] = 10
- 0以上の整数型
val zero: Int Refined NonNegative = 0
val overZero: Int Refined NonNegative = 10
// コンパイルエラー
//val nonNegative: Int Refined NonNegative = -10
- 特定の文字から始まる文字列型
val startWithS: String Refined StartsWith[W.`"S"`.T] = "Start"
// コンパイルエラー
//val startWithS: String Refined StartsWith[W.`"S"`.T] = "Goal"
- 特定の文字で終わる文字列型
val endWithS: String Refined EndsWith[W.`"l"`.T] = "Goal"
// コンパイルエラー
//val endWithS: String Refined EndsWith[W.`"S"`.T] = "Start"
備考
-
EndsWith[W.?.T]のように[W.?.T]がついているところは、直感的にEndsWith["S"]のように書きたいところですが、それだと動かないので注意が必要です。