takafumi blog

日々の勉強メモ

scala3: Matchableとパターンマッチ

環境   Scala 3.1.2

scala3では AnyAnyVal & AnyRef の間に Matchable というタイプが追加された。

A First Look at Types | Scala 3 — Book | Scala Documentation

scala3_class_hierarchy

理由はこちら

The Matchable Trait

何が変わったのか?

この変更は ` immutable に定義したタイプがパターンマッチで不変性が破壊されてしまう事への対処として導入された。

簡単に言うとパターンマッチのセレクター、 C(_)_ :C のような、には Matchable が実装される必要がある。

一番影響がありそうなのは、型パラメータが Matchable を実装しないとパターンマッチにでWarningが出る事だろう。

例として以下のようなメソッドがある。

def matchableTest[T](t: T) =
  t match {
    case _: Int => println("Int")
    case _      => println("Other")
}

scala2であれば当然問題ない。しかし型パラーメーター TMatchable かどうかの保証がないため、scala3ではWarningになる。

sbt:scala3> compile
[info] compiling 1 Scala source to /path/to/target/scala-3.1.2/classes ...
[warn] -- [E165] Type Warning: /path/to/src/main/scala/Main.scala:7:11
[warn] 7 |      case _: Int => println("Int")
[warn]   |           ^^^^^^
[warn]   |           pattern selector should be an instance of Matchable,,
[warn]   |           but it has unmatchable type T instead
[warn] one warning found

つまり TT <: Matchable としてやる必要がある。

// OK
def matchableTest[T <: Matchable](t: T) =
  t match {
    case _: Int => println("Int")
    case _      => println("Other")
}

またこの問題は 型 C[T] をパターンマッチした時も発生する

case class Foo[T](val t: T)

def matchableTest2[T](foo: Foo[T]) =
  foo match {
    case Foo(i: Int) => println(i)
    case Foo(_) => println("other")
sbt:scala3> compile
[info] compiling 1 Scala source to /path/to/target/scala-3.1.2/classes ...
[warn] -- [E165] Type Warning: /path/to/src/main/scala/Main.scala:16:18
[warn] 16 |      case Foo(i: Int) => println(i)
[warn]    |                  ^^^
[warn]    |                  pattern selector should be an instance of Matchable,,
[warn]    |                  but it has unmatchable type T instead
[warn] one warning found

また余談だが、 scala2 だと List[Any] となる以下のような場合 scala3 だと List[Matchable] になる

scala> List(1, "a")
val res1: List[Matchable] = List(1, a)

Matchable のメンバ

現状 Matchable はメンバを持たない

abstract class Any:
  def isInstanceOf
  def getClass
  def asInstanceOf      // Cast to a new type: myAny.asInstanceOf[String]
  def ==
  def !=
  def ##   // Alias for hashCode
  def equals
  def hashCode
  def toString

trait Matchable extends Any

class AnyVal extends Any, Matchable

class AnyRef extends Any, Matchable

が将来的には getClassgetInstanceOfMatchable に移動する可能性もあるらしい。

Matchable is currently a marker trait without any methods. Over time we might migrate methods getClass and isInstanceOf to it, since these are closely related to pattern-matching.

-source:future とscala2互換性

と説明を書いたが、この機能はデフォルトではscala3でも互換性を考慮しWarningを出さない。 scalacOptions-source:future を追加する事で有効になる。