2010-01-01 15 views
19

¿Por qué esta impresión wtf? ¿La coincidencia de patrones no funciona en tipos estructurales?Tipos estructurales coincidentes con el patrón en Scala

"hello" match { 
    case s: { def doesNotExist(i: Int, x: List[_]): Double } => println("wtf?") 
    case _ => println("okie dokie") 
    } 
+0

he ampliado mi respuesta a la luz de su comentario :). –

Respuesta

18

ejecutar este ejemplo en el intérprete Scala con advertencias sin marcar en (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 la JVM no tiene genéricos reificados.

Todo lo que el JVM ve en esta coincidencia de patrón es:

"hello" match { 
    case s: Object => ... 
    case annon: Object => ... 
} 

EDIT: En respuesta a sus comentarios, he estado pensando en una solución, pero no tienen el tiempo para publicar ayer . Desafortunadamente, incluso si funciona, el compilador no puede inyectar el Manifest adecuado.

El problema que desea resolver es comparar si un objeto es de un tipo estructural dado. Aquí hay un código que he estado pensando en Scala (2,8-r20019, como Scala 2.7.6.final estrellado 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 esa es mi línea de pensamiento. Lamentablemente, esto no se puede compilar, ya que el compilador inyecta un Manifest[AnyRef] en lugar de un Manifest[Foo] al llamar al getManifest[Foo]. Curiosamente, si no usa un tipo estructural (por ejemplo, type Foo = String), este código se compila y funciona como se espera. En algún momento, publicaré una pregunta 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 se 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 tiene un método def foo(x: {def foo: Int}) = x.foo, después del borrado obtiene def foo(x: Object) = [some reflection invoking foo on x], y se pierde la información del tipo. Es por eso que se usa la reflexión en primer lugar, ya que debe invocar un método en un Object y la JVM no sabe si el Object tiene ese método.

+0

Gracias, Flaviu. Eso responde mi pregunta. Pero todavía me deja pensando cuál sería la mejor manera de lograr esto, porque la estructura es algo que realmente está disponible en tiempo de ejecución a través de la reflexión. Es simplemente torpe llegar a eso. –

+0

Gracias por el seguimiento. Cosas muy interesantes –

7

Si usted va a tener que utilizar la reflexión, al menos puede hacer que se vea más bonita 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") 
} 
+0

Sería bueno si se pudiera definir 'HasFoo' de manera más flexible, como' val HasFoo = new Tiene [{def foo(): Unit}] ("foo") '.Intenté hacerlo de esa manera, pero todavía parece haber algunos problemas con respecto a tipos más complicados como '{def foo (i: Int): Int}'. – Debilski

+0

¿Por qué el compilador no hace esto automáticamente? – Gabriel

+0

'containsMethod' se puede deshidratar a' Try (x.getClass.getMethod (name, params: _ *)). IsSuccess' –

Cuestiones relacionadas