Patrones que coinciden con los tipos estructurales en Scala
pattern-matching structural-typing (2)
La ejecución de este ejemplo en el intérprete de Scala con advertencias sin scala -unchecked
( scala -unchecked
) produce la siguiente advertencia: warning: refinement AnyRef{def doesNotExist(Int,List[_]): Double} in type pattern is unchecked since it is eliminated by erasure
. Desafortunadamente, un tipo genérico como este no puede verificarse en tiempo de ejecución ya que la JVM no tiene genéricos reificados.
Todo lo que ve la JVM en esta coincidencia de patrón es:
"hello" match {
case s: Object => ...
case annon: Object => ...
}
EDITAR: En respuesta a sus comentarios, he estado pensando en una solución pero no tuve tiempo para publicarla ayer. Desafortunadamente, incluso si funcionara, el compilador falla al inyectar el Manifest
apropiado.
El problema que desea resolver es comparar si un objeto es de un tipo estructural dado. Aquí hay un código en el que he estado pensando (Scala 2.8-r20019, ya que Scala 2.7.6. Final se estrelló en mí un par de veces mientras jugaba con ideas similares)
type Foo = AnyRef { def doesNotExist(i: Int, x: List[_]): Double }
def getManifest[T](implicit m: Manifest[T]) = m
def isFoo[T](x: T)(implicit mt: Manifest[T]) =
mt == getManifest[Foo]
El método isFoo
básicamente compara los manifiestos de la clase x
de Foo
. En un mundo ideal, el manifiesto de un tipo estructural debe ser igual al manifiesto de cualquier tipo que contenga los métodos requeridos. Al menos esa es mi línea de pensamiento. Desafortunadamente, esto no se compila, ya que el compilador inyecta un Manifest[AnyRef]
lugar de un Manifest[Foo]
cuando llama a getManifest[Foo]
. Curiosamente, si no usa un tipo estructural (por ejemplo, type Foo = String
), este código compila y funciona como se esperaba. Publicaré una pregunta en algún momento para ver por qué esto falla con los tipos estructurales, ¿es una decisión de diseño o es solo un problema de la API de reflexión experimental?
Si falla, siempre puede usar la reflexión de Java para ver si un objeto contiene un método.
def containsMethod(x: AnyRef, name: String, params: java.lang.Class[_]*) = {
try {
x.getClass.getMethod(name, params: _*)
true
}
catch {
case _ => false
}
}
que funciona como se espera:
containsMethod("foo", "concat", classOf[String]) // true
containsMethod("foo", "bar", classOf[List[Int]]) // false
... pero no es muy agradable
Además, tenga en cuenta que la estructura de un tipo estructural no está disponible en tiempo de ejecución. Si tiene un método def foo(x: {def foo: Int}) = x.foo
, después del borrado, obtendrá def foo(x: Object) = [some reflection invoking foo on x]
, la información de tipo se pierde. Es por eso que la reflexión se usa en primer lugar, ya que tiene que invocar un método en un Object
y la JVM no sabe si el Object
tiene ese método.
¿Por qué esta impresión wtf? ¿La coincidencia de patrones no funciona en tipos estructurales?
"hello" match {
case s: { def doesNotExist(i: Int, x: List[_]): Double } => println("wtf?")
case _ => println("okie dokie")
}
Si vas a tener que usar la reflexión, al menos puedes hacer que se vea mejor con un extractor:
object WithFoo {
def foo(){
println("foo was called")
}
}
object HasFoo {
def containsMethod(x: AnyRef, name: String, params: Array[java.lang.Class[_]]) : Boolean = {
try {
x.getClass.getMethod(name, params: _*)
true
} catch {
case _ => false
}
}
def unapply(foo:AnyRef):Option[{def foo():Unit}] = {
if (containsMethod(foo, "foo", new Array[Class[_]](0))) {
Some(foo.asInstanceOf[{def foo():Unit}])
} else None
}
}
WithFoo.asInstanceOf[AnyRef] match {
case HasFoo(foo) => foo.foo()
case _ => println("no foo")
}