🥰
Scala 3 の Match Types で再帰型を表現する.
再帰型、つまり type T = T | S
のような型を Scala 3 の Match Types を使って表現する.
再帰型が使えると例えば List("hello",List("world"),"!")
にも List("hello","world","!") にも List(List("hello"),"world",List("!")) にもマッチするような型が作れる.
Scala 3 では A | B というふうに Union 型が使えるので型 S を再帰の終端として T = T | S
と書けるんちゃうか?と思っていた時期がありました😇
残念ながら type T = T | S
と書ければいいがこれは illegal cyclic type reference
となってエラーになります. ぴえん.
そこで Scala のユーザーグループに書かれていたチョットしたハックを使う.
build.sbt を書く.
build.sbt
scalaVersion := "3.0.2"
scalacOptions += "-Yexplicit-nulls"
次のような型を定義する.
type Rec[F[_],A] = A match
case String => String | F[Rec[F,String]]
case _ => A | F[Rec[F,A]]
こうすると次のコードはすべてコンパイルが通る(どうして....😇)
val list0:Rec[List,String] = List("hello",List("world"),"!")
val list1:Rec[List,String] = List("hello","world","!")
val list2:Rec[List,String] = List(List("hello"),"world",List("!"))
val list3:Rec[List,String] = List(List(List(List(List("hello")))),"world",List("!"))
一方で List(1)
などはちゃんとコンパイルエラーになる.
これを応用して Rec
型を次のように定義することで Json を表現することもできる.
opaque type JNull = Unit
opaque type JPrimitive = Int | String | Boolean | JNull
type JRec[JArray[_],JObj[_],A] =
A match
case JPrimitive => JPrimitive | JArray[JRec[JArray,JObj,JPrimitive]] | JObj[JRec[JArray,JObj,JPrimitive]]
case _ => A | JArray[JRec[JArray,JObj,A]] | JObj[Rec[JArray,JObj,A]]
type JValue = Rec[Seq,[A] =>> Map[String,A],JPrimitive]
val x0 :JValue = 1 // Ok
val x1 :Jvalue = 1d // => コンパイルエラー
val x1 :JValue = true // Ok
val x2:JValue = Seq(1,"str",false) // Ok
val x3 :JValue = Map("k0"->1,"k1"->Map("k1-1"->Map("k1-1-1"->List(false,1,"2")))) // Ok!
val x4 :JValue = Map("k0"->1,"k1"->Map("k1-1"->Map("k1-1-1"->List(false,1d,"2")))) // => コンパイルエラー
val x1 :JValue = null // => コンパイルエラー
型安全で夢が広がりますね(^ω^)
(正直に言うとこれの使い道をあまり思いつかない...)
以下の記事を参考にした(コドュマーロってなんだ...?🤔)
Discussion