2010-09-08 38 views
11

W wariancie Scala można zdefiniować wariancję z operatorami wariancji, takimi jak + i - na typowym argumencie typu. Na przykład typ List jest kowariancyjny w bibliotece standardowej.Podczas korzystania z notacji kowariancji lub ogólnych ograniczeń w Scali

class List[+A] 

Więc funkcja z listy kowariantna można zdefiniować tak:

def foo[A](list : List[A]) 

także wariancji może być emulowane z granic rodzajowych. Tak więc możemy również napisać ten

def foo[A](list : List[_:< A]) 

oczywiście to nie ma sensu, ponieważ list jest już kowariantna. Ale tę samą sztuczkę można zrobić dla typów, które nie są kowariantne. (jak Stack). Oczywiście można również tworzyć nowe typy ze stosu (dziedziczenie agregacji), który jest kowariantny.

więc moje pytania:

  1. Kiedy należy używać granic rodzajowych dla wariancji? A kiedy powinniśmy stworzyć nowy typ kowariancyjny?
  2. Czy ogólne ograniczenia są przydatne tylko w przypadku wariancji, czy mogą zadeklarować więcej (koncepcje językowe).
  3. Jeśli są one użyteczne tylko dla wariancji, czy granice to tylko dla zgodności z Javą?

thx z góry :)

+1

Podwojenie tego pytania - wariancja jest najcięższą częścią Scali i właściwie nie rozumiem tego całkiem dobrze. "Blah-bla covariant bla pojawił się w pozycji contravariant blah-blah": p – Jeriho

+2

Zrozumiałem wariancję, gdy spojrzałem na definicje niektórych cech funkcji (funkcja 1, funkcja 2 itd.) Cechy funkcji są kowariantne na typie wyjściowym i contravariant na typach wejściowych (parametr). Na przykład funkcja typu "Any => Unit" może być użyta wszędzie tam, gdzie oczekiwana jest funkcja typu 'String => Unit', ponieważ funkcja' Any => Unit' może przyjmować ciąg znaków jako dane wejściowe (ponieważ może to brać wszystko jako dane wejściowe.) Z tego powodu typy parametrów metody są "przeciwstawną pozycją". –

Odpowiedz

13

Jeśli typ jest naturalnie kowariantny lub sprzeczny z oryginałem, należy go o tym zadeklarować. Twoi użytkownicy będą Ci za to wdzięczni. Wariancja miejsca użycia jest w istocie przeważnie obecna z powodu Javy. Dokładniej, rodzaj takich jak Array[T <: Number] jest traktowany jako skrót dla typu egzystencjalnego:

ArrayBuffer[T] forSome { type T <: Number } 

egzystencjalne typy mają dość nieporęczne składnię w Scala. Jest to celowe, ponieważ nie zalecamy używania ich zbyt wiele. Kiedy potrzebowałbyś typu egzystencjalnego?

  1. Aby napisać analog typu Java z symbolami wieloznacznymi, na przykład List<? extends Number>.
  2. Aby napisać analogiczny typ Java raw, taki jak List.

W Javie, surowe rodzaje i wieloznaczne typy nie są zupełnie takie same i nie jest zupełnie taka sama jak typu egzystencjalnego (choć wiemy, co oni nie są, to dość trudno precyzyjnie określić, jakie one są).Są jednak na tyle blisko egzystencjalnych w praktyce, że Scala nie ma odwzorowania ich na tego typu.

6
  1. Przy tworzeniu nowego typu rodzajowego, powiedzmy Foo [T], należy spróbować trudno określić, czy typ jest kowariantna, kontrawariantny lub niezmienna i zadeklarować foo [ + T], Foo [-T] lub Foo [T]. Wprawdzie może to być trochę trudne. Uwalnia jednak użytkownika Foo od podejmowania tej decyzji za każdym razem, gdy musi użyć Foo, używając ogólnych ograniczeń. W skrócie: preferuj wariancję miejsca deklaracji względem wariancji strony wywołania, gdy wariancja jest właściwością samego typu.

BTW, książka Programowanie w Scali autorstwa Martina Odersky'ego, Lex Spoon i Billa Vennersa ma kilka wspaniałych sekcji dotyczących wariancji. Zobacz rozdział 19 Parametryzacja typu.