web-dev-qa-db-ja.com

Scalaでジェネリック型のパターンマッチングを行う方法

汎用クラスContainerがあるとしましょう:

case class Container[+A](value: A)

次に、ContainerDoubleおよび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を照合する方法は?

27

一般的にラリーの答えは正しいですが、あなたの場合は単純化できます。コンテナにはジェネリック型の単一の値しか含まれていないため、その値の型を直接照合できます。

container match {
  case Container(x: String) => println("string")
  case Container(x: Double) => println("double")
  case _ => println("w00t")
}
30
drexin

多分これは役立つでしょう

 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")
    }
28
rarry

これに対する可能な回避策は、isInstanceOfasInstanceOfを使用することです。

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教授は、isInstanceOfasInstanceOfは避けるべきだと述べています。

Rob Norrisが私に指摘したように、Courseraからのコース「Functional programming in Scala」のフォーラムでは、タイプによるマッチングは悪い習慣です:case foo: Bar => ...。 Scalaは、静的型付けを利用し、実行時の型チェックを回避することを奨励します。これは、Haskell/MLの世界の哲学と一致しています。typescase句はコンストラクターと一致する必要があります。

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つのオブジェクトStringContainerDoubleContainerで定義し、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コースに登録していない場合はアクセスできません。

12

注: 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キャストがリストの値に基づいているためです。リストが空の場合、当然、それは有効な文字列リストと有効な整数リスト(またはそのほかの任意のリスト)です。

5
VonC