2013-04-10 10 views
9

Mam pewne problemy motywujące użycie klas typów w Scali, gdy porówna się z górnymi granicami typów.Jaka jest motywacja dla klas typów w Scali?

Rozważmy następujący kod:

case class NumList[T <: Complex](xs: Complex*) { 
    def sum = (xs fold new Complex(0, 0))(_ + _) 
    def map[U <: Complex](f: Complex => U): NumList[U] = NumList(xs.map(f): _*) 
    override def toString = "[" + xs.mkString(", ") + "]" 
    } 

    case class GenList[T](xs: T*) { 
    def sum(implicit num: Numeric[T]) = xs.sum 
    def map[U](f: T => U) = GenList(xs.map(f): _*) 
    override def toString = "[" + xs.mkString(", ") + "]" 
    } 

    val r = new Real(2) 
    val n = new Natural(10) 
    val comps = NumList(r, n, r, n) 

    println(comps) 
    println("sum: " + comps.sum) 
    println("sum * 2: " + comps.map(x => x + x).sum) 

    val comps2 = GenList(4, 3.0, 10l, 3d) 
    println(comps2) 
    println("sum: " + comps2.sum) 
    println("sum * 2: " + comps2.map(_ * 2).sum) 

Chociaż te dwie listy rozwiązać podobne problemy, jeden używa numeryczną typu klasy, a drugi wykorzystuje górną granicę parametru type. Rozumiem różnice techniczne dość dobrze, ale ciężko jest mi dotrzeć do podstawowej motywacji klas typu. Najlepszą motywacją, jaką znalazłem do tej pory, jest:

Podczas gdy tworzenie podklas lub implementowanie interfejsów pozwala na wykonywanie w większości tych samych projektów, klasy typów pozwalają na określenie cech typu na podstawie metody, podczas gdy klasa ogólna z typem T i górna granica U ogranicza wszędzie, gdzie jest używana, ograniczenie T. Mając to na uwadze, klasy typów zapewniają dokładniejszą kontrolę nad cechami T w klasach ogólnych.

Czy są jakieś bardzo wyraźne przykłady motywujące wzór?

Odpowiedz

9

Próbując uprościć jeden główny aspekt, typografy próbują zebrać zachowanie niezależnie od hierarchii klas.

Załóżmy, że musisz zdefiniować nowy numeryczny typ MetaNum (ze standardowymi operacjami numerycznymi), ale nie możesz lub nie będzie to podklasa typu Complex, bez względu na przyczynę.

Po typografice Numeric wystarczy podać odpowiednią instancję dla swojego numeru MetaNum, podając wymagane operacje.

Następnie można utworzyć GenList[MetaNum] i zsumować go.

Nie można tego zrobić z NumList, ponieważ MetaNum nie jest Complex. Wybór implementacji dokonany podczas definiowania NumList zaatakuje cię, gdy spróbujesz uogólnić swoją operację/strukturę danych w drugiej chwili.

Wnioski
Typeclasses daje większą swobodę przedłużyć swoje zachowanie niezależnie od rozważań hierarchicznych, kosztem pewnej dodatkowej złożoności i boilerplate.

Nie mogę powiedzieć, czy masz na myśli to samo w pytaniu.

+2

To jest dokładne uzasadnienie. Ogólnie, Klasy typów mogą być bardziej zwięzłe, niż w Scali. Sposób, w jaki są one kodowane, pozwala na ich użycie z dużo większą elastycznością, wtedy można to zrobić w coś w rodzaju waniliowego Haskella, ale implementacja Haskella jest dużo bardziej zwięzła. Innym dobrym przykładem jest dodanie do hierarchii czegoś w rodzaju Serializacji, dodając interfejs do górnej części hierarchii, która rozbije wszystkie stare klasy. Korzystanie z klas typów pozwala na stopniową implementację funkcjonalności, a konkretnie tylko na potrzebnych typach. – jroesch

+2

Tak jak powiedział @jroesch, warto podkreślić fakt, że typografia jest zaimplementowana w scala jako wzorzec (to znaczy nie jest to funkcja językowa), ale pojęcie pochodzi z innych języków (takich jak haskell), w których funkcja jest osadzona w sam język. E.g nie jest językiem OO, nie ma mechanizmu dziedziczenia, więc funkcja typeclass służy do uzyskiwania wspólnej funkcji poprzez implementację funkcji niestandardowych na różnych typach danych.Dlatego też typografia może wydawać się zbędna w scala, gdzie występuje nakładanie się funkcji z dziedziczeniem obiektów. –

+0

Bardzo dobrze. Zostawiłem to na jakiś czas i wróciłem do pytania na własną rękę, starając się to zmotywować. Wymyśliłem bardzo podobny argument. Miły! – Felix

Powiązane problemy