2010-01-01 10 views
19

Pourquoi est-ce que cette impression wtf? L'appariement de modèles ne fonctionne-t-il pas sur les types structurelsTypes de structure de correspondance de modèle dans Scala

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

J'ai développé ma réponse à la lumière de votre commentaire :). –

Répondre

18

L'exécution de cet exemple sur (scala -unchecked) dans l'interpréteur Scala avec des avertissements non vérifiées produit l'avertissement suivant: warning: refinement AnyRef{def doesNotExist(Int,List[_]): Double} in type pattern is unchecked since it is eliminated by erasure. Malheureusement, un type générique comme celui-ci ne peut pas être vérifié à l'exécution car la JVM n'a pas de génériques réifiés.

Tout ce que la machine virtuelle Java voit dans ce match de modèle est:

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

EDIT: En réponse à vos commentaires, j'ai pensé à une solution, mais n'a pas eu le temps de poster hier . Malheureusement, même si doit fonctionner, le compilateur n'injecte pas le bon Manifest.

Le problème que vous voulez résoudre est de comparer si un objet est d'un type structurel donné. Voici un code que j'ai pensé (Scala 2.8-r20019, comme Scala 2.7.6.final écrasé sur moi deux ou trois fois tout en jouant avec des idées similaires)

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éthode isFoo compare essentiellement les manifestes du classe x de Foo. Dans un monde idéal, le manifeste d'un type structurel doit être égal au manifeste de tout type contenant les méthodes requises. Au moins c'est mon train de pensée. Malheureusement, ceci échoue à compiler, car le compilateur injecte un Manifest[AnyRef] au lieu d'un Manifest[Foo] en appelant le getManifest[Foo]. Fait intéressant, si vous n'utilisez pas un type structurel (par exemple, type Foo = String), ce code compile et fonctionne comme prévu. Je vais poster une question à un moment donné pour voir pourquoi cela échoue avec les types structurels - est-ce une décision de conception, ou c'est juste un problème de l'API de réflexion expérimentale. A défaut, vous pouvez toujours utiliser la réflexion Java pour voir si un objet contient une méthode.

def containsMethod(x: AnyRef, name: String, params: java.lang.Class[_]*) = { 
    try { 
    x.getClass.getMethod(name, params: _*) 
    true 
    } 
    catch { 
    case _ => false 
    } 
} 

qui fonctionne comme prévu:

containsMethod("foo", "concat", classOf[String]) // true 
containsMethod("foo", "bar", classOf[List[Int]]) // false 

... mais ce n'est pas très agréable.

Notez également que la structure d'un type structurel n'est pas disponible au moment de l'exécution. Si vous avez une méthode def foo(x: {def foo: Int}) = x.foo, après l'effacement vous obtenez def foo(x: Object) = [some reflection invoking foo on x], les informations de type étant perdues. C'est pourquoi la réflexion est utilisée en premier lieu, car vous devez invoquer une méthode sur un Object et la JVM ne sait pas si le Object a cette méthode.

+0

Merci, Flaviu. Cela répond à ma question. Mais je me demande toujours quelle serait la meilleure façon d'y arriver, car la structure est quelque chose qui est réellement disponible à l'exécution par la réflexion. C'est juste maladroit d'y arriver. –

+0

Merci pour le suivi. Des trucs très intéressants. –

7

Si vous allez devoir utiliser la réflexion, vous pouvez au moins le rendre plus joli avec un extracteur:

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

Serait bien si on pouvait définir 'HasFoo' de manière plus flexible, comme' has Has Has = new Has [{def foo(): Unit}] ("foo") '.J'ai juste essayé de le faire de cette façon, mais il semble toujours y avoir quelques problèmes concernant des types plus compliqués tels que '{def foo (i: Int): Int}'. – Debilski

+0

Pourquoi le compilateur ne le fait-il pas automatiquement? – Gabriel

+0

'containsMethod' peut être déshydraté en Try (x.getClass.getMethod (name, params: _ *)). IsSuccess' –