devin-avery-ZsgPd6ovNag-unsplash.jpg

Match Type

 
0
このエントリーをはてなブックマークに追加
Kazuki Moriyama
Kazuki Moriyama (森山 和樹)

dottyの型システムに現れた強力な機能の一つにMatch Typeがある。
これによって2系のscalaだと信じられないくらい複雑なコードが必要だったものが簡潔になる。

Match Typeとは

受け取った型にパターンマッチをかけて指定の型にマッピングできる強力な機能である。
TypeScriptのconditional typesに近い。

type ToString[X] = X match
  case String => "String"
  case Int    => "Int"

summon[ToString[String] =:= "String"]
summon[ToString[Int] =:= "Int"]

この様に通常のマッチケース式と同様の形で型をマッピングできる。
やばい。
ちなみにcaseで指定していない型を入れると具体型に還元されないようだがコンパイルは通る。

summon[ToString[Long] <:< Any]

上限境界

Match Typeは上限境界を持つことができる。
例えば以下の状況ではコンパイルが通らない。

type ListLike[X] = X match
  case String => List[String]
  case Int    => List[List[Int]]

def head[X](xs: ListLike[X]) = xs.head

// [error] 19 |def head[X](xs: ListLike[X]) = xs.head
// [error]    |                               ^^^^^^^
// [error]    |                               value head is not a member of ListLike[X]

これは必ずしもListLikeがListであるとは確定していないためである。
Match Typeに上限境界を付けてあげるとコンパイルが通る。

type ListLike[X] <: List[_] = X match
  case String => List[String]
  case Int    => List[List[Int]]

def head[X](xs: ListLike[X]) = xs.head

パターン中の型変数

Match Typeでもパターン中の一部を変数として抜き出すことができる。
これは普通のマッチケース式と同様である。

type Content[X] = X match
  case List[a]   => a
  case Map[?, a] => a

summon[Content[List[Int]] =:= Int]
summon[Content[Map[String, String]] =:= String]

ただしパターン中の変数は小文字で始める必要がある。

型レベル階乗計算

Match Typeを使用すれば複雑な計算をシンプルに記述することができる。
例えば2系だと死ぬほど面倒だった階乗計算も以下のようにかんたん。

type Factorial[X <: Int] <: Int = X match
  case 0    => 1
  case S[i] => X * Factorial[i]

summon[Factorial[0] =:= 1]
summon[Factorial[3] =:= 6]

終わり

Match TypeはまさにScalaの型レベル計算への革命である。

info-outline

お知らせ

K.DEVは株式会社KDOTにより運営されています。記事の内容や会社でのITに関わる一般的なご相談に専門の社員がお答えしております。ぜひお気軽にご連絡ください。