scala pattern-matching type-erasure

¿Cómo coinciden los patrones en el tipo genérico en Scala?



pattern-matching type-erasure (4)

En general, la respuesta de rarry es correcta, sin embargo, para su caso se puede simplificar, ya que su contenedor solo contiene un único valor de un tipo genérico, por lo que puede coincidir directamente con el tipo de ese valor:

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

Supongamos que tenemos un Container clase genérico:

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

Entonces queremos que el patrón coincida con un Container con un Double y un Container de Any :

val double = Container(3.3) var container: Container[Any] = double

Para hacer esto, normalmente escribiríamos:

container match { case c: Container[String] => println(c.value.toUpperCase) case c: Container[Double] => println(math.sqrt(c.value)) case _ => println("_") }

Sin embargo, el compilador da dos advertencias, una para cada uno de los primeros dos casos. Por ejemplo, la primera advertencia dice: "argumento de tipo no variable Cadena en el patrón de tipo Contenedor [Cadena] no se selecciona porque se elimina por borrado". Debido a la eliminación, es imposible durante el tiempo de ejecución distinguir entre diferentes tipos de contenedores y la primera captura se igualará. Como consecuencia, el contenedor del tipo Container[Double] se combinará con el primer caso, que atrapa los objetos del Container[String] , por lo que se llamará al método toUpperCase en un Double y se lanzará una java.lang.ClassCastException .

¿Cómo hacer coincidir un Container parametrizado por un tipo particular?


Nota: también tiene una alternativa con la biblioteca Shapeless de Miles Sabin ( ya mencionada por Miles en 2012 aquí ).

Puede ver un ejemplo en " Formas de coincidencia de patrones genéricos en Scala " de Jaakko Pallari

Typeable es una clase de tipo que proporciona la capacidad de convertir valores de Any tipo a un tipo específico .
El resultado de la operación de lanzamiento es una Option donde el valor Some contendrá el valor arrojado con éxito, y el valor None representa una falla de lanzamiento.

Puentes Typeable y coincidencia de patrones. Es esencialmente un extractor para instancias de 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.

Aunque Typeable puede parecer que tiene lo que se necesita para resolver el borrado de tipo, aún está sujeto al mismo comportamiento que cualquier otro código de tiempo de ejecución.
Esto se puede ver en las últimas líneas de los ejemplos de código previos donde las listas vacías se reconocieron como listas de cadenas incluso cuando se especificaron como listas de enteros. Esto se debe a que los moldes Typeable se basan en los valores de la lista. Si la lista está vacía, entonces naturalmente esa es una lista de cadenas válida y una lista de enteros válida (o cualquier otra lista para el caso)


Tal vez esto ayude

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

Editar:

Como Impredicative señaló, Manifiesto está en desuso. En cambio, podrías hacer lo siguiente:

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


Una solución posible para esto podría ser utilizar isInstanceOf y 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("_") }

Esto funciona, pero no se ve elegante en absoluto. El profesor Martin Odersky, el creador de Scala, dice que se debe evitar isInstanceOf y como asInstanceOf .

Como Rob Norris me señaló, en el foro del curso " Programación funcional en Scala " de Coursera, emparejar por tipo es una mala práctica: case foo: Bar => ... Scala recomienda aprovechar el tipado estático y evitar el tipo de comprobación durante el tiempo de ejecución. Esto es consistente con la filosofía del mundo de Haskell / ML. En lugar de coincidir con los tipos , las cláusulas de case deben coincidir con los constructores .

Para resolver el problema de coincidencia de Container , se puede definir un contenedor especial para cada tipo:

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)

Y ahora los constructores serán emparejados, no los tipos :

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

Aparentemente, podríamos definir los métodos de unapply en dos objetos, StringContainer y DoubleContainer y usar la misma coincidencia que antes, en lugar de extender la clase 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) }

Pero esto no funciona, nuevamente, debido al borrado de tipo JVM.

Una referencia a la publicación de Rob Norris, que me llevó a esta respuesta se puede encontrar aquí: https://class.coursera.org/progfun-002/forum/thread?thread_id=842#post-3567 . Desafortunadamente, no puede acceder a él a menos que esté inscrito en el curso de Coursera.