2014-05-02 11 views
12

Mi stavo scambiando email con un conoscente che è un grande fan di Kotlin, Clojure e Java8 e gli ho chiesto perché non Scala. Ha fornito molte ragioni (Scala è troppo accademico, troppi aspetti, non la prima volta che sento questo e penso che questo sia molto soggettivo) ma il suo più grande punto dolente è stato ad esempio, che non gli piace una lingua dove lui non può capire l'implementazione delle strutture dati di base e ha dato LinkedList come esempio.Comprensione di GenericTraversableTemplate e altri interni della collezione Scala

Ho dato un'occhiata a scala.collection.LinkedList e contato le cose che capisco o in qualche modo capisco.

  • CanBuildFrom - dopo qualche sforzo, ho capito, le classi di tipo, non il suicidio più lunga nella storia [1]
  • LinkedListLike - non mi ricordo dove l'ho letto, ma mi sono convinto che questo è lì per una buona ragione

Ma poi ho cominciato a fissare questi

  • GenericTraversableTemplate - ora sto graffiare la mia testa così ...
  • SeqFactory, GenericCompanion - OK, ora mi perso, comincio a capire il suo punto

può qualcuno che capisce questo bene spiegare GenericTraversableTemplateSeqFactory e GenericCompanion nel contesto di LinkedList? A cosa servono, quale impatto hanno per l'utente finale (per esempio sono sicuro che ci sono per una buona ragione, qual è questo motivo?)

Sono lì per un motivo pratico? o è un livello di astrazione che avrebbe potuto essere semplificato?

Mi piacciono le collezioni Scala perché non devo capire gli interni per poterli utilizzare in modo efficace. Non mi dispiace un'implementazione complessa se mi aiuta a mantenere il mio utilizzo più semplice. per esempio. Non mi interessa pagare il prezzo di una biblioteca complessa se ottengo la possibilità di scrivere un codice più pulito più elegante usandolo in cambio. ma sarà sicuramente bello comprenderlo meglio.

[1] - Is the Scala 2.8 collections library a case of "the longest suicide note in history"?

+0

[We're Do It All Wrong] (http://youtu.be/TS1lpKBMkgg?t=14m26s) –

+0

Sì, e la versione leggermente modificata: [Scala Collections: Why Not?] (Https: // www .youtube.com/watch? v = uiJycy6dFSQ) –

risposta

4

cercherò di descrivere i concetti dal punto di vista di un pedone casuale (non ho mai contribuito una sola riga alla biblioteca raccolta Scala, quindi non mi ha colpito troppo difficile se sbaglio).

Dato che LinkedList è obsoleto, e poiché Maps fornisce un esempio migliore, userò TreeMap come esempio.

CanBuildFrom

La motivazione è questo: se prendiamo un TreeMap[Int, Int] e mappa con

case (x, y) => (2 * x, y * y * 0.3d) 

otteniamo TreeMap[Int, Double]. Questo tipo di sicurezza da solo spiegherebbe già la necessità dei costrutti semplici genericBuilder[X]. Tuttavia, se mapparla con

case (x, y) => x 

si ottiene un Iterable[Int] (più precisamente: un List[Int]), questo non è più una mappa, il tipo di contenitore è cambiato. Questo è dove CBF del entrano in gioco:

CanBuildFrom[This, X, That] 

può essere visto come una sorta di "funzione di tipo-level" che ci dice: se tracciamo una collezione di tipo questo con una funzione che restituisce valori di tipo X , possiamo costruire un That. La più specifica CBF è previsto al momento della compilazione, nel primo caso sarà qualcosa di simile a

CanBuildFrom[TreeMap[_,_], (X,Y), TreeMap[X,Y]] 

nel secondo caso sarà qualcosa di simile a

CanBuildFrom[TreeMap[_,_], X, Iterable[X]] 

e così abbiamo sempre ottenere la giusta tipo del contenitore. Il modello è piuttosto generale. Ogni volta che si dispone di una funzione generica

foo[X1, ..., Xn](x1: X1, ..., xn: Xn): Y 

in cui il tipo di risultato Y dipende X1, ..., Xn, si può introdurre un parametro implicito come segue:

foo[X1, ...., Xn, Y](x1: X1, ..., xn: Xn)(implicit CanFooFrom[X1, ..., Xn, Y]): Y 

e quindi definire la funzione a livello di testo X1, ..., Xn -> Y a tratti fornendo più CanfooFrom di implicito.

LinkedListLike

nella definizione della classe, si vede qualcosa di simile:

TreeMap[A, B] extends SortedMap[A, B] with SortedMapLike[A, B, TreeMap[A, B]] 

Questo è il modo di Scala per esprimere il cosiddetto polimorfismo F-delimitata. La motivazione è la seguente: Supponiamo di avere una dozzina (o almeno due ...) implementazioni del tratto SortedMap[A, B]. Ora vogliamo implementare un metodo withoutHead, potrebbe sembrare un po 'come questo:

def withoutHead = this.remove(this.head) 

Se ci spostiamo l'attuazione in SortedMap[A, B] stesso, il meglio che possiamo fare è questo:

def withoutHead: SortedMap[A, B] = this.remove(this.head) 

Ma è questo è il tipo di risultato più specifico che possiamo ottenere? No, è troppo vago. Vorremmo restituire TreeMap[A, B] se la mappa originale è TreeMap e CrazySortedLinkedHashMap (o qualsiasi altra cosa ...) se l'originale era un CrazySortedLinkedHashMap. È per questo che ci muoviamo l'attuazione in SortedMapLike, e diamo la seguente firma per il metodo withoutHead:

trait SortedMapLike[A, B, Repr <: SortedMap[A, B]] { 
    ... 
    def withoutHead: Repr = this.remove(this.head) 
} 

ora perché TreeMap[A, B] estende SortedMapLike[A, B, TreeMap[A, B]], il tipo di risultato di withoutHead è TreeMap[A,B]. Lo stesso vale per CrazySortedLinkedHashMap: otteniamo il tipo esatto indietro.In Java, si dovrebbe attuare una politica tornare SortedMap[A, B] o l'override del metodo in ogni sottoclasse (che si è rivelato essere un incubo di manutenzione per i tratti ricchi di funzionalità a Scala)

GenericTraversableTemplate

Il tipo è: GenericTraversableTemplate[+A, +CC[X] <: GenTraversable[X]]

Per quanto posso dire, questo è solo una caratteristica che fornisce implementazioni di metodi che restituiscono qualche modo collezioni regolari con lo stesso tipo di contenitore, ma possibilmente diverso tipo di contenuto (roba come flatten, transpose, unzip).

Stuff come foldLeft, reduce, exists non sono qui perché questi metodi riguardano solo il tipo di contenuto, non il tipo di contenitore.

Stuff come flatMap non è qui, perché il tipo di contenitore può cambiare (di nuovo, CBF).

Perché è un tratto separato, esiste una ragione fondamentale per cui esiste? Io non la penso così ... Probabilmente sarebbe possibile raggruppare la godzillion dei metodi in qualche modo in modo diverso. Ma questo è proprio ciò che accade in modo naturale: si inizia a implementare un tratto e si scopre che ha molti metodi. Così, invece di raggruppare metodi vagamente correlati, e metterli in 10 diversi tratti con nomi scomodi come "GenTraversableTemplate", e li li mescolano in tratti/classi dove ne avete bisogno ...

GenericCompanion

questa è solo una classe astratta che implementa alcune funzionalità di base che è comune per gli oggetti compagno della maggior parte delle classi di raccolta (in sostanza, solo che implementa molto semplici metodi di fabbrica apply(varargs) e empty).

Per esempio c'è il metodo apply che prende varargs di qualche tipo A e restituisce un insieme di tipo CC[A]:

Array(1, 2, 3, 4) // calls Array.apply[A](elems: A*) on the companion object 
List(1, 2, 3, 4) // same for List 

L'implementazione è molto semplice, si tratta di qualcosa di simile:

def apply[A](varargs: A*): CC[A] = { 
    val builder = newBuilder[A] 
    for (arg <- varargs) builder += arg 
    builder.result() 
} 

Ovviamente è lo stesso per Array e Liste e TreeMaps e quasi tutto il resto, eccetto "Collezioni irregolari vincolate" come Bitset. Quindi questa è solo una funzionalità comune in una classe di antenati comune della maggior parte degli oggetti associati. Niente di speciale in questo.

SeqFactory

Simile a GenericCompanion, ma questa volta più specificamente per le sequenze. aggiunge alcuni metodi di fabbrica comuni come fill() e iterate() e tabulate() ecc Anche in questo caso, niente di particolarmente razzo-scientifica qui ...

osservazioni generali Pochi

In generale: non credo che si dovrebbe cercare di capire ogni singolo tratto in questa biblioteca. Piuttosto, si dovrebbe cercare di guardare la biblioteca nel suo insieme. Nel complesso, ha un'architettura molto interessante. E secondo la mia personale opinione, è in realtà un software molto estetico, ma bisogna guardarlo per un po '(e provare a ri-implementare l'intero modello architettonico più volte) per afferrarlo. D'altra parte: per esempio i CBF sono una specie di "modello di progettazione" che chiaramente dovrebbe essere eliminato nei successori di questa lingua. L'intera storia con lo scopo implicito di CBF sembra ancora un incubo per me. Ma all'inizio molte cose sembravano completamente imperscrutabili, e quasi sempre finivano con un'epifania (che è molto specifica per Scala: per la maggior parte delle altre lingue, queste lotte di solito finiscono con il pensiero "Autore di questo è un completo idiota") .

+0

Ottima risposta. Questo mi ricorda in qualche modo la digitazione anatra di 'python' e quello che devi implementare ma con un sistema di tipo reale. –

Problemi correlati