汎用クラスContainer
があるとしましょう:
case class Container[+A](value: A)
次に、Container
とDouble
およびContainer
of Any
をパターンマッチさせます。
val double = Container(3.3)
var container: Container[Any] = double
これを行うには、通常次のように記述します。
container match {
case c: Container[String] => println(c.value.toUpperCase)
case c: Container[Double] => println(math.sqrt(c.value))
case _ => println("_")
}
ただし、コンパイラは2つの警告を出します。最初の2つのケースのそれぞれに対して1つです。たとえば、最初の警告は、「型パターンContainer [String]の非変数型引数Stringは消去によって削除されているため、チェックされていません」と述べています。消去のため、実行時に異なる種類のコンテナーを区別することは不可能であり、最初のキャッチが一致します。結果として、タイプContainer[Double]
のコンテナーは、Container[String]
オブジェクトをキャッチする最初のケースと一致するため、toUpperCase
メソッドがDouble
で呼び出され、 Java.lang.ClassCastException
がスローされます。
特定の型によってパラメータ化されたContainer
を照合する方法は?
一般的にラリーの答えは正しいですが、あなたの場合は単純化できます。コンテナにはジェネリック型の単一の値しか含まれていないため、その値の型を直接照合できます。
container match {
case Container(x: String) => println("string")
case Container(x: Double) => println("double")
case _ => println("w00t")
}
多分これは役立つでしょう
def matchContainer[A: Manifest](c: Container[A]) = c match {
case c: Container[String] if manifest <:< manifest[String] => println(c.value.toUpperCase)
case c: Container[Double] if manifest <:< manifest[Double] => println(math.sqrt(c.value))
case c: Container[_] => println("other")
}
編集:
Impredicativeが指摘したように、マニフェストは非推奨です。代わりに、以下を実行できます。
import reflect.runtime.universe._
def matchContainer[A: TypeTag](c: Container[A]) = c match {
case c: Container[String] if typeOf[A] <:< typeOf[String] => println("string: " + c.value.toUpperCase)
case c: Container[Double] if typeOf[A] <:< typeOf[Double] => println("double" + math.sqrt(c.value))
case c: Container[_] => println("other")
}
これに対する可能な回避策は、isInstanceOf
とasInstanceOf
を使用することです。
container match {
case Container(x) if x.isInstanceOf[String] =>
println(x.asInstanceOf[String].toUpperCase)
case Container(x) if x.isInstanceOf[Double] =>
println(math.sqrt(x.asInstanceOf[Double]))
case _ => println("_")
}
これは機能しますが、エレガントに見えません。 Scalaの作成者であるMartin Odersky教授は、isInstanceOf
とasInstanceOf
は避けるべきだと述べています。
Rob Norrisが私に指摘したように、Courseraからのコース「Functional programming in Scala」のフォーラムでは、タイプによるマッチングは悪い習慣です:case foo: Bar => ...
。 Scalaは、静的型付けを利用し、実行時の型チェックを回避することを奨励します。これは、Haskell/MLの世界の哲学と一致しています。types、case
句はコンストラクターと一致する必要があります。
Container
マッチング問題を解決するために、タイプごとに特別なコンテナーを定義できます。
class Container[+A](val value: A)
case class StringContainer(override val value: String)
extends Container(value)
case class DoubleContainer(override val value: Double)
extends Container(value)
そして今コンストラクターはtypesではなく一致します:
container match {
case StringContainer(x) => println(x.toUpperCase)
case DoubleContainer(x) => println(math.sqrt(x))
case _ => println("_")
}
どうやら、unapply
メソッドを2つのオブジェクトStringContainer
とDoubleContainer
で定義し、Container
クラスを拡張する代わりに、上記と同じ一致を使用できます。
case class Container[+A](val value: A)
object StringContainer {
def unapply(c: Container[String]): Option[String] = Some(c.value)
}
object DoubleContainer {
def unapply(c: Container[Double]): Option[Double] = Some(c.value)
}
ただし、JVMタイプの消去のため、これは機能しません。
この答えに私を導くロブ・ノリスの投稿への参照はここにあります: https://class.coursera.org/progfun-002/forum/thread?thread_id=842#post-3567 。残念ながら、Courseraコースに登録していない場合はアクセスできません。
注: Miles Sabin 's Shapeless library ( すでにMilesが2012年に言及している )を使用した代替方法もあります。
「 Scalaのジェネリック型をパターンマッチングする方法 」の例を Jaakko Pallari から確認できます。
Typeable
は、Any
タイプから特定のタイプに値をキャストする機能を提供するタイプクラスです。
キャスト操作の結果はOption
で、Some
値には正常にキャストされた値が含まれ、None
値はキャストの失敗を表します。
TypeCase
ブリッジTypeable
とパターンマッチング。本質的にはTypeable
インスタンスのエクストラクターです
import shapeless._
def extractCollection[T: Typeable](a: Any): Option[Iterable[T]] = {
val list = TypeCase[List[T]]
val set = TypeCase[Set[T]]
a match {
case list(l) => Some(l)
case set(s) => Some(s)
case _ => None
}
}
val l1: Any = List(1, 2, 3)
val l2: Any = List[Int]()
val s: Any = Set(1, 2, 3)
extractCollection[Int](l1) // Some(List(1, 2, 3))
extractCollection[Int](s) // Some(Set(1, 2, 3))
extractCollection[String](l1) // None
extractCollection[String](s) // None
extractCollection[String](l2) // Some(List()) // Shouldn't this be None? We'll get back to this.
Typeable
は型の消去を解決するために必要な機能を備えているように見えますが、それでも他のランタイムコードと同じ動作の影響を受けます。
これは、前のコード例の最後の行で確認できます。空のリストは、整数リストとして指定されている場合でも、文字列リストとして認識されていました。これは、Typeable
キャストがリストの値に基づいているためです。リストが空の場合、当然、それは有効な文字列リストと有効な整数リスト(またはそのほかの任意のリスト)です。