2010-01-01 11 views
19

Dlaczego to drukuje wtf? Czy porównywanie wzorców nie działa w przypadku typów strukturalnych?Dopasowane do wzoru typy strukturalne w Scali

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

I Rozszerzyliśmy moją odpowiedź w świetle Twojego komentarza :). –

Odpowiedz

18

Uruchomienie tego przykład w tłumacza Scala z niekontrolowanych ostrzeżeń na (scala -unchecked) wywołuje następujące ostrzeżenie: warning: refinement AnyRef{def doesNotExist(Int,List[_]): Double} in type pattern is unchecked since it is eliminated by erasure. Niestety typowy typ taki jak ten nie może być sprawdzony w czasie wykonywania, ponieważ JVM nie ma reifikowanych generycznych.

Wszystko to JVM widzi w tym meczu jest wzór:

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

EDIT: W odpowiedzi na komentarze, myślałem o rozwiązanie, ale nie mają czasu, aby opublikować go wczoraj . Niestety, nawet jeśli działa powinien działać, kompilator nie wstrzykuje odpowiedniego Manifest.

Problem, który chcesz rozwiązać, to porównanie, czy obiekt ma określony typ konstrukcyjny. Oto niektóre kodu Myślałam o Scala (2,8 r20019, jak Scala 2.7.6.final rozbił się na mnie kilka razy podczas gry z podobnych pomysłów)

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] 

Method isFoo zasadzie porównuje manifesty klasa x z Foo. W idealnym świecie manifest typu strukturalnego powinien być równy manifestowi dowolnego typu zawierającego wymagane metody. Przynajmniej to mój ciąg myśli. Niestety, ta kompilacja nie powiedzie się, ponieważ kompilator wstrzykuje Manifest[AnyRef] zamiast Manifest[Foo] podczas wywoływania getManifest[Foo]. Co ciekawe, jeśli nie używasz typu strukturalnego (na przykład type Foo = String), ten kod kompiluje się i działa zgodnie z oczekiwaniami. W pewnym momencie opublikuję pytanie, aby zobaczyć, dlaczego to się nie udaje z typami strukturalnymi - jest to decyzja projektowa lub jest to tylko problem eksperymentalnego interfejsu API do refleksji.

W przeciwnym razie zawsze można użyć refleksji Java, aby sprawdzić, czy obiekt zawiera metodę.

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

który działa zgodnie z oczekiwaniami:

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

... ale to nie jest bardzo miłe.

Należy również zauważyć, że struktura typu strukturalnego nie jest dostępna w środowisku wykonawczym. Jeśli masz metodę def foo(x: {def foo: Int}) = x.foo, po usunięciu otrzymujesz def foo(x: Object) = [some reflection invoking foo on x], informacja o typie jest tracona. Właśnie dlatego odbicie jest używane w pierwszej kolejności, ponieważ musisz wywołać metodę na Object, a JVM nie wie, czy ta metoda ma wartość Object.

+0

Dzięki, Flaviu. To odpowiada na moje pytanie. Nadal jednak zastanawiam się, jaki byłby najlepszy sposób na osiągnięcie tego celu, ponieważ struktura jest dostępna w czasie rzeczywistym dzięki refleksji. To po prostu niezdarne. –

+0

Dzięki za kontynuację. Bardzo interesujące rzeczy. –

7

Jeśli masz zamiar użyć refleksji, można przynajmniej sprawiają, że wygląda ładniej z ekstraktora:

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

Byłoby miło, gdyby można było zdefiniować 'HasFoo' bardziej elastycznie, na przykład' val HasFoo = new Has [{def foo(): Unit}] ("foo") '.Po prostu starałem się to zrobić w ten sposób, ale nadal istnieją pewne problemy dotyczące bardziej skomplikowanych typów, takich jak '{def foo (i: Int): Int}'. – Debilski

+0

Dlaczego kompilator nie robi tego automatycznie? – Gabriel

+0

'zawieraMetoda' może być odwodniona do' Spróbuj (x.getClass.getMethod (nazwa, params: _ *)) .sSuccess' –