2013-04-17 16 views
25

Załóżmy, że mamy rodzajowe klasy Container:Jak dopasować wzór na typowy w Scali?

case class Container[+A](value: A) 

Następnie chcesz wzór dopasować Container z Double i Container z Any:

val double = Container(3.3) 
var container: Container[Any] = double 

Aby to zrobić, będziemy normalnie pisać :

container match { 
    case c: Container[String] => println(c.value.toUpperCase) 
    case c: Container[Double] => println(math.sqrt(c.value)) 
    case _ => println("_") 
} 

Jednak kompilator podaje dwa ostrzeżenia , po jednym dla każdego z pierwszych dwóch przypadków. Na przykład pierwsze ostrzeżenie mówi: "argument typu nie zmiennego Łańcuch w wzorzec typu Kontener [Ciąg] nie jest zaznaczony, ponieważ został usunięty przez wymazanie". Ze względu na wymazanie w czasie wykonywania nie jest możliwe rozróżnienie między różnymi rodzajami kontenerów, a pierwszy połów zostanie dopasowany. W konsekwencji, pojemnik typu Container[Double] zostanie dopasowany przez pierwszy przypadek, który przechwytuje obiekty Container[String], więc metoda zostanie wywołana na Double i zostanie wyrzucony java.lang.ClassCastException.

Jak dopasować parametr Container sparametryzowany przez określony typ?

+0

Dodałem tam odpowiedź na to samo pytanie: [link] (http://stackoverflow.com/questions/35181533/can-we-elegantly-match-an-erased-type-in-scala/35181934#35181934) – dth

Odpowiedz

11

Możliwe obejście tego problemu może być użycie isInstanceOf i asInstanceOf.

container match { 
    case Container(x) if x.isInstanceOf[String] => 
    println(x.asInstanceOf[String].toUpperCase) 
    case Container(x) if x.isInstanceOf[Double] => 
    println(math.sqrt(x.asInstanceOf[Double])) 
    case _ => println("_") 
} 

To działa, ale to nie wygląda elegancki w ogóle. Profesor Martin Odersky, twórca Scala, mówi, że należy unikać stosowania isInstanceOf i asInstanceOf.

Jako Rob Norris wskazał mnie na forum kursu „programowania funkcyjnego w Scala” z Coursera, dopasowując według typu jest złą praktyką: case foo: Bar => .... Scala zachęca do korzystania z pisania statycznego i unikania sprawdzania typu w czasie wykonywania. Jest to zgodne z filozofią świata Haskell/ML. Zamiast dopasowywania typów, case klauzule powinny być zgodne z konstruktorami.

celu rozwiązania problemu związanego z dopasowaniem Container, specjalny pojemnik do każdego typu można zdefiniować:

class Container[+A](val value: A) 

case class StringContainer(override val value: String) 
    extends Container(value) 

case class DoubleContainer(override val value: Double) 
    extends Container(value) 

a teraz konstruktorzy będą dopasowane, a nie rodzajów:

container match { 
    case StringContainer(x) => println(x.toUpperCase) 
    case DoubleContainer(x) => println(math.sqrt(x)) 
    case _ => println("_") 
} 

Najwidoczniej , możemy zdefiniować metody unapply w dwóch obiektach, StringContainer i DoubleContainer i użyć tego samego dopasowania co powyżej, inst EAD przedłużenia klasę Container:

case class Container[+A](val value: A) 

object StringContainer { 
    def unapply(c: Container[String]): Option[String] = Some(c.value) 
} 


object DoubleContainer { 
    def unapply(c: Container[Double]): Option[Double] = Some(c.value) 
} 

Ale to nie działa, ponownie, ze względu na typ JVM skasowaniem.

Odwołanie do postu Rob Norris, które doprowadziło mnie do tej odpowiedzi, można znaleźć tutaj: https://class.coursera.org/progfun-002/forum/thread?thread_id=842#post-3567. Niestety, nie możesz uzyskać do niego dostępu, chyba że jesteś zapisany na kurs Coursera.

24

Może to pomoże

def matchContainer[A: Manifest](c: Container[A]) = c match { 
     case c: Container[String] if manifest <:< manifest[String] => println(c.value.toUpperCase) 
     case c: Container[Double] if manifest <:< manifest[Double] => println(math.sqrt(c.value)) 
     case c: Container[_] => println("other") 
    } 

EDIT:

Jak Impredicative podkreślił Oczywisty jest nieaktualna. Zamiast tego można wykonać następujące czynności:

import reflect.runtime.universe._ 
def matchContainer[A: TypeTag](c: Container[A]) = c match { 
     case c: Container[String] if typeOf[A] <:< typeOf[String] => println("string: " + c.value.toUpperCase) 
     case c: Container[Double] if typeOf[A] <:< typeOf[Double] => println("double" + math.sqrt(c.value)) 
     case c: Container[_] => println("other") 
    } 
+3

Warto zauważyć, że 'Manifest' jest zastąpiona w' TypeTag' najnowsze wersje Scali. – Impredicative

+2

powinno być bezpieczeństwo wątków teraz: https://github.com/scala/scala/pull/3386 – Cal

29

W odpowiedzi na ogólne rarry jest prawidłowe, w Twoim przypadku jednak może być uproszczona, ponieważ pojemnik zawiera tylko jedną wartość dla typu rodzajowego, więc można dopasować na tę wartość na wpisać bezpośrednio:

container match { 
    case Container(x: String) => println("string") 
    case Container(x: Double) => println("double") 
    case _ => println("w00t") 
} 
3

Uwaga: można również mieć alternatywę z Miles Sabin „s Shapeless library (already mentioned by Miles in 2012 here).

można zobaczyć przykład w „Ways to pattern match generic types in Scala” z Jaakko Pallari

Typeable to klasa typu, który zapewnia możliwość oddania wartości od Any typu do konkretnego typu.
Wynikiem operacji odlewania jest Option gdzie wartość Some będzie zawierać powodzeniem odlewano wartości, a wartość None oznacza awarię plastikowymi.

TypeCase mostki Typeable i dopasowanie wzorca. Jest to w istocie wyciąg dla Typeable przypadkach

import shapeless._ 

def extractCollection[T: Typeable](a: Any): Option[Iterable[T]] = { 
    val list = TypeCase[List[T]] 
    val set = TypeCase[Set[T]] 
    a match { 
    case list(l) => Some(l) 
    case set(s) => Some(s) 
    case _  => None 
    } 
} 

val l1: Any = List(1, 2, 3) 
val l2: Any = List[Int]() 
val s: Any = Set(1, 2, 3) 

extractCollection[Int](l1) // Some(List(1, 2, 3)) 
extractCollection[Int](s)  // Some(Set(1, 2, 3)) 
extractCollection[String](l1) // None 
extractCollection[String](s) // None 
extractCollection[String](l2) // Some(List()) // Shouldn't this be None? We'll get back to this. 

Podczas Typeable może wyglądać ma to, czego potrzeba, aby rozwiązać typu skasowaniu, nadal podlegają tym samym zachowanie jak każdym innym kodem wykonawczym.
Można to zobaczyć w ostatnich wierszach poprzednich przykładów kodu, gdzie puste listy zostały rozpoznane jako listy ciągów, nawet jeśli zostały określone jako listy całkowite. Dzieje się tak, ponieważ rzutowanie Typeable opiera się na wartościach z listy. Jeśli lista jest pusta, to oczywiście jest to poprawna lista napisów i poprawna lista całkowitoliczbowa (lub jakakolwiek inna lista).

+0

prowadzi link artykuł naprawdę daje doskonałą traktowania tego tematu, a także obejmuje inne alternatywy (odbicie wykonania, etc.) – Luciano