Как сопоставить шаблон по родовому типу в Scala?

Предположим, что мы имеем общий класс Container:

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

Затем мы хотим сопоставить шаблон Container с Double и Container 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("_")  
}

Однако компилятор дает два предупреждения: по одному для каждого из первых двух случаев. Например, первое предупреждение говорит: "Аргумент типа non-variable String в шаблоне типа Container [String] не отмечен, поскольку он устраняется стиранием". Из-за стирания во время выполнения невозможно различать различные типы контейнеров, и первый улов будет согласован. Как следствие, контейнер типа Container[Double] будет сопоставляться первым случаем, который ловит Container[String] объекты, поэтому метод toUpperCase будет вызываться в Double, а a java.lang.ClassCastException будет выбрано.

Как сопоставить параметр Container, параметризованный определенным типом?

Ответ 1

В общем случае правильный ответ правильный, для вашего случая, однако, его можно упростить, потому что ваш контейнер содержит только одно значение родового типа, поэтому вы можете напрямую сопоставлять этот тип значения:

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

Ответ 2

Возможно, это поможет

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

Edit:

Как указывал 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")
    }

Ответ 3

Возможным обходным путем для этого может быть использование 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, говорит, что следует избегать isInstanceOf и asInstanceOf.

Как сказал мне Роб Норрис, на форуме курса "Функциональное программирование в Scala" из Coursera совпадение по типу - это плохая практика: case foo: Bar => .... Scala рекомендует использовать статическую типизацию и избегать проверки типа во время выполнения. Это согласуется с философией мира Haskell/ML. Вместо сопоставления типов предложения 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)

И теперь конструкторы будут сопоставлены, а не типы:

container match {
  case StringContainer(x) => println(x.toUpperCase)
  case DoubleContainer(x) => println(math.sqrt(x))
  case _ => println("_")
}

По-видимому, мы могли бы определить методы unapply в двух объектах 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. К сожалению, вы не можете получить к нему доступ, если вы не зачислены на курс Курсеры.

Ответ 4

Примечание. У вас также есть альтернатива Miles SabinБесформенная библиотека (уже упоминалось Майлсом в 2012 году здесь).

Вы можете увидеть пример в "Способах сопоставления шаблонных типов в Scala" от Яакко Паллари

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 основаны на значениях списка. Если список пуст, то, естественно, это допустимый список строк и допустимый список целых чисел (или любой другой список в этом отношении)