Pregunta

¿Por qué esto imprime 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")
  }
¿Fue útil?

Solución

Ejecutando este ejemplo en el intérprete de Scala con advertencias sin control sobre (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 se puede verificar en tiempo de ejecución ya que el JVM no tiene genéricos reificados.

Todo lo que el JVM ve en esta coincidencia de patrones 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 de publicarla ayer. Desafortunadamente, incluso si debería Trabajar, el compilador no puede inyectar el Manifest.

El problema que desea resolver es comparar si un objeto es de un tipo estructural dado. Aquí hay algún código en el que he estado pensando (Scala 2.8-R20019, ya que Scala 2.7.6.Final se estrelló sobre 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]

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 ese es mi tren de pensamiento. Desafortunadamente, esto no se compila, ya que el compilador inyecta un Manifest[AnyRef] en lugar de un Manifest[Foo] Al llamar getManifest[Foo]. Curiosamente, si no usa un tipo estructural (por ejemplo, type Foo = String), este código se 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.

De lo contrario, 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 esperaba:

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 tienes un método def foo(x: {def foo: Int}) = x.foo, después de la borrado que obtienes def foo(x: Object) = [some reflection invoking foo on x], la información de tipo que se está perdiendo. Es por eso que la reflexión se usa en primer lugar, ya que tienes que invocar un método en un Object y el JVM no sabe si el Object tiene ese método.

Otros consejos

Si va a tener que usar la reflexión, al menos puede 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")
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top