2010-01-01 18 views
19

Perché questa stampa wtf? La corrispondenza del modello non funziona sui tipi strutturali?Tipi di motivi strutturali corrispondenti in Scala

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

ho ampliato la mia risposta alla luce del tuo commento :). –

risposta

18

L'esecuzione di questo esempio, l'interprete Scala con avvisi incontrollati su (scala -unchecked) produce il seguente avviso: warning: refinement AnyRef{def doesNotExist(Int,List[_]): Double} in type pattern is unchecked since it is eliminated by erasure. Sfortunatamente, un tipo generico come questo non può essere verificato in fase di esecuzione poiché la JVM non ha generici reificati.

Tutto ciò che la JVM vede in questa partita modello è:

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

EDIT: In risposta ai vostri commenti, ho pensato a una soluzione, ma non ha avuto il tempo di postare ieri . Sfortunatamente, anche se dovrebbe funzionare, il compilatore non riesce a iniettare l'appropriato Manifest.

Il problema che si desidera risolvere è confrontare se un oggetto è di un determinato tipo strutturale. Ecco un po 'di codice che ho pensato di (Scala 2,8-r20019, come Scala 2.7.6.final si è schiantato su di me un paio di volte durante il gioco con idee simili)

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] 

Metodo isFoo confronta fondamentalmente i manifesti delle classe x di Foo. In un mondo ideale, il manifest di un tipo strutturale dovrebbe essere uguale al manifest di qualsiasi tipo contenente i metodi richiesti. Almeno questo è il mio pensiero. Sfortunatamente questo non riesce a compilare, poiché il compilatore inietta un Manifest[AnyRef] invece di un Manifest[Foo] quando chiama getManifest[Foo]. È interessante notare che, se non si utilizza un tipo strutturale (ad esempio, type Foo = String), questo codice viene compilato e funziona come previsto. A un certo punto pubblicherò una domanda per capire perché questo fallisce con i tipi strutturali - è una decisione di progettazione, o è solo un problema dell'API di riflessione sperimentale.

In caso contrario, è possibile utilizzare sempre il riflesso Java per verificare se un oggetto contiene un metodo.

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

che funziona come previsto:

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

... ma non è molto bello.

Inoltre, si noti che la struttura di un tipo strutturale non è disponibile in fase di esecuzione. Se si dispone di un metodo def foo(x: {def foo: Int}) = x.foo, dopo la cancellazione si ottiene def foo(x: Object) = [some reflection invoking foo on x], le informazioni sul tipo vanno perse. Ecco perché il reflection viene utilizzato in primo luogo, poiché devi invocare un metodo su un Object e la JVM non sa se lo Object ha quel metodo.

+0

Grazie, Flaviu. Questo risponde alla mia domanda. Ma mi lascia ancora a chiedermi quale sarebbe il modo migliore per ottenerlo, perché la struttura è qualcosa che è effettivamente disponibile in fase di esecuzione attraverso la riflessione. È solo maldestro per arrivarci. –

+0

Grazie per il follow-up. Roba molto interessante –

7

Se si sta andando ad avere per utilizzare la riflessione, si può almeno far sembrare più bello con un estrattore:

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

Sarebbe bello se fosse in grado di definire 'HasFoo' in modo più flessibile, come' val HasFoo = new Has [{def foo(): Unit}] ("foo") '.Ho solo provato a farlo in questo modo, ma sembra che ci siano ancora dei problemi riguardo a tipi più complicati come '{def foo (i: Int): Int}'. – Debilski

+0

Perché il compilatore non lo fa automaticamente? – Gabriel

+0

'containsMethod' può essere disidratato in' Try (x.getClass.getMethod (name, params: _ *)). IsSuccess' –

Problemi correlati