【Scala】値制約（Refined依存型）をmatchのcase条件節で使う方法：case classを使え！



以前書いた記事では、ユーザ定義の依存型のクラス名（type別名）を使って、型レベルのパターンマッチを試みに成功しませんでした。

https://qiita.com/electronics_diy721/items/391ab4b9e1d78ee3a7a6

Refinedで定義した値制約に、__*type*キーワード__で__別名*ConditionedInt*__を付けた後、

>```Scala:sbtConsole
>scala> type ConditionedInt = Int Refined Interval.ClosedOpen[10000, 10000000]
>type ConditionedInt
>
>scala>
>```

case節のマッチング対象に、Stringと並んで、__*ConditionedInt*__を指定すると、認識されない。

>```Scala:sbtConsole
>scala> def class_check(a: Any) = {
>     |   a match {
>    （省略）
>     |     case _: ConditionedInt => "有料顧客の課金額"
>
>```

__（ エラー表示 ）__

>```Scala:sbtConsole
           case _: 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
>```


今回、以下の方法で成功することが分かりましたので、記事にしてみました。

1. Refinedを使って、値制約をユーザ定義する。定義した値条件に__*type*キーワード__で別名をつける。
-  上記の別名型を持つオブジェクトを受け取るコンストラクタが定義されたcase classを、ユーザ定義クラスとして用意。
- matchのcase節に、IntやStringやList[String], List[Int]と並べて、上記のcase classのクラス名をセットする。


###Step １

Refinedを使って、値制約をユーザ定義する。定義した値条件に__*type*キーワード__で別名をつける。

```Scala:sbtConsole
scala> type ConditionedInt = Int Refined Interval.ClosedOpen[10000, 10000000]
type ConditionedInt

scala>
```

###Step ２

上記の別名型を持つオブジェクトを受け取るコンストラクタが定義されたcase classを、ユーザ定義クラスとして用意する。

```Scala:sbtConsole
scala> case class DefinedInt(number: ConditionedInt) {
     |   val numeric = number
     |   def getNumber = numeric
     |   println(numeric)
     | }
class DefinedInt

scala> 
```

- 挙動を確認。
- 確かに、定義した値制約条件がちゃんと動いている。

```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> 
```

- インスタンスメソッドも動く

```Scala:sbtConsole
scala> new_number.getNumber
val res9: ConditionedInt = 2000000
```

###Step ３

- match文のcase節に、IntやStringやList[String], List[Int]と並べて、上記のcase classのクラス名をセットする。

```Scala:sbtConsole
scala> def class_check(a: Any) = {
     |  a match {
     |    case _: String => "文字列"
     |    case _: DefinedInt => "値が指定範囲内の数値"
     |    case _ => "不明"
     |  }
     | }
def class_check(a: Any): String

scala> 
```

###Step ４

- 値制約を充足する数値（例：__*2000000*__）を初期化変数として、__*DefinedInt*クラス__のコンストラクタに渡して、__*DefinedInt*型オブジェクトのインスタンス__を生成する。
- 生成されたインスタンス・オブジェクトを、match式に渡す。
- 意図した通り、__*値が指定範囲内の数値*__というメッセージ（String型）が出力された。

```Scala: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節__には合致しない。

```Scala:sbtConsole
scala> class_check(1000000)
val res11: String = 不明

scala> 
```

####以下もOK

```Scala: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節で認識されない

```Scala: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> 
```


```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> 
```

```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節として認識されない。

```Scala:sbtConsole
scala> class_check(1000000)
val res6: String = 普通の整数

scala>
```

- 以下も同様にダメ。

```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> 
```

___

##実行環境

####インポートした外部ライブラリ

```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*ファイル__の記述内容

```text:build.sbt
scalaVersion := "2.13.6"
libraryDependencies += "eu.timepit" %% "refined" % "0.9.27"
```