以前書いた記事では、ユーザ定義の依存型のクラス名(type別名)を使って、型レベルのパターンマッチを試みに成功しませんでした。
Refinedで定義した値制約に、typeキーワードで別名ConditionedIntを付けた後、
sbtConsolescala> type ConditionedInt = Int Refined Interval.ClosedOpen[10000, 10000000] type ConditionedInt scala>
case節のマッチング対象に、Stringと並んで、ConditionedIntを指定すると、認識されない。
sbtConsolescala> def class_check(a: Any) = { | a match { (省略) | case _: ConditionedInt => "有料顧客の課金額"
( エラー表示 )
sbtConsolecase _: ConditionedInt => "有料顧客の課金額" ^ On line 4: warning: non-variable type argument Int in type pattern eu.timepit.refined.api.Refined[Int,eu.timepit.refined.numeric.Interval.ClosedOpen[10000,10000000]] (the underlying of ConditionedInt) is unchecked since it is eliminated by erasure
今回、以下の方法で成功することが分かりましたので、記事にしてみました。
- Refinedを使って、値制約をユーザ定義する。定義した値条件にtypeキーワードで別名をつける。
- 上記の別名型を持つオブジェクトを受け取るコンストラクタが定義されたcase classを、ユーザ定義クラスとして用意。
- matchのcase節に、IntやStringやList[String], List[Int]と並べて、上記のcase classのクラス名をセットする。
Step 1
Refinedを使って、値制約をユーザ定義する。定義した値条件にtypeキーワードで別名をつける。
sbtConsole
scala> type ConditionedInt = Int Refined Interval.ClosedOpen[10000, 10000000]
type ConditionedInt
scala>
Step 2
上記の別名型を持つオブジェクトを受け取るコンストラクタが定義されたcase classを、ユーザ定義クラスとして用意する。
sbtConsole
scala> case class DefinedInt(number: ConditionedInt) {
| val numeric = number
| def getNumber = numeric
| println(numeric)
| }
class DefinedInt
scala>
- 挙動を確認。
- 確かに、定義した値制約条件がちゃんと動いている。
sbtConsole
scala> val new_number = new DefinedInt(2000000)
2000000
val new_number: DefinedInt = DefinedInt(2000000)
scala> val new_number = new DefinedInt(200)
^
error: Left predicate of (!(200 < 10000) && (200 < 10000000)) failed: Predicate (200 < 10000) did not fail.
scala>
- インスタンスメソッドも動く
sbtConsole
scala> new_number.getNumber
val res9: ConditionedInt = 2000000
Step 3
- match文のcase節に、IntやStringやList[String], List[Int]と並べて、上記のcase classのクラス名をセットする。
sbtConsole
scala> def class_check(a: Any) = {
| a match {
| case _: String => "文字列"
| case _: DefinedInt => "値が指定範囲内の数値"
| case _ => "不明"
| }
| }
def class_check(a: Any): String
scala>
Step 4
- 値制約を充足する数値(例:2000000)を初期化変数として、DefinedIntクラスのコンストラクタに渡して、DefinedInt型オブジェクトのインスタンスを生成する。
- 生成されたインスタンス・オブジェクトを、match式に渡す。
- 意図した通り、値が指定範囲内の数値というメッセージ(String型)が出力された。
sbtConsole
scala> val new_number = new DefinedInt(2000000)
2000000
val new_number: DefinedInt = DefinedInt(2000000)
scala>
scala> class_check(new_number)
val res10: String = 値が指定範囲内の数値
scala>
( 挙動の補足 )
- 数値リテラルで、数値範囲内にある数値を渡すと、文字列(String)として認識される。
- newキーワードで生成したDefinedInt型クラスのインスタンスオブジェクトを渡さないと、DefinedInt型のcase節には合致しない。
sbtConsole
scala> class_check(1000000)
val res11: String = 不明
scala>
以下もOK
sbtConsole
scala> def class_check2(a: Any) = {
| a match {
| case _: String => "文字列"
| case _: DefinedInt => "値が指定範囲内の数値"
| case _: Int => "普通の整数"
| case _ => "不明"
| }
| }
def class_check2(a: Any): String
scala>
scala> class_check2(new_number)
val res12: String = 値が指定範囲内の数値
scala> class_check2(1000000)
val res13: String = 普通の整数
scala> class_check2(10000000)
val res14: String = 普通の整数
scala> class_check2(100)
val res15: String = 普通の整数
scala> class_check2("abc")
val res16: String = 文字列
scala> class_check2('a')
val res17: String = 不明
scala> class_check2(List(1, 2, 3))
val res18: String = 不明
scala>
普通のユーザ定義クラスは、match文の中のcase節で認識されない
sbtConsole
scala> type ConditionedInt = Int Refined Interval.ClosedOpen[10000, 10000000]
type ConditionedInt
scala> class numberConditionedInt(val number: ConditionedInt) {
| val num = number
| def get_number: ConditionedInt = num
| }
class numberConditionedInt
scala>
sbtConsole
scala> val newConditionedNumber = new numberConditionedInt(200000)
val newConditionedNumber: numberConditionedInt = numberConditionedInt@66354fc2
scala> println(newConditionedNumber)
$line15.$read$$iw$numberConditionedInt@66354fc2
scala> newConditionedNumber
val res2: numberConditionedInt = numberConditionedInt@66354fc2
scala> newConditionedNumber.get_number
val res3: ConditionedInt = 200000
scala> println(newConditionedNumber.get_number)
200000
scala> val newConditionedNumber = new numberConditionedInt(200)
^
error: Left predicate of (!(200 < 10000) && (200 < 10000000)) failed: Predicate (200 < 10000) did not fail.
scala>
sbtConsole
scala> def class_check(a: Any) = {
| a match {
| case _: String => "文字列"
| case _: numberConditionedInt => "値が指定範囲内の数値"
| case _: Int => "普通の整数"
| case _ => "不明"
| }
| }
def class_check(a: Any): String
scala>
- numberConditionedInt型の値制約を充足する数値1000000)を与えても、numberConditionedInt型のcase節として認識されない。
sbtConsole
scala> class_check(1000000)
val res6: String = 普通の整数
scala>
- 以下も同様にダメ。
sbtConsole
scala> def class_check2(a: Any) = {
| a match {
| case _: String => "文字列"
| case _: numberConditionedInt => "値が指定範囲内の数値"
| case _ => "不明"
| }
| }
def class_check2(a: Any): String
scala>
scala> class_check2(1000000)
val res7: String = 不明
scala>
実行環境
インポートした外部ライブラリ
sbtConsole
scala> import eu.timepit.refined._
import eu.timepit.refined._
scala> import eu.timepit.refined.api.Refined
import eu.timepit.refined.api.Refined
scala> import eu.timepit.refined.auto._
import eu.timepit.refined.auto._
scala> import eu.timepit.refined.numeric._
import eu.timepit.refined.numeric._
scala>
Refinedのバージョン:2.13.6
- build.sbtファイル_の記述内容
build.sbt
scalaVersion := "2.13.6"
libraryDependencies += "eu.timepit" %% "refined" % "0.9.27"