2012-10-17 11 views
10

Nel Twitter Effective Scala - Type Aliases, dicono:buon stile: Tipo alias vs sottoclasse vuote classi/tratti

Non utilizzare sottoclassi quando un alias farà.

trait SocketFactory extends (SocketAddress => Socket) 

a SocketFactory è una funzione che produce uno zoccolo. Utilizzando un tipo alias

type SocketFactory = SocketAddress => Socket 

è meglio. Possiamo ora fornire letterali di funzione per i valori di tipo SocketFactory e anche utilizzare la funzione di composizione: val addrToInet: SocketAddress => Long val inetToSocket: Lungo => Socket

val factory: SocketFactory = addrToInet andThen inetToSocket 

notare che la tipologia alias non sono nuovi tipi - che sono equivalenti alla sostituzione sintattica del nome aliasato per il suo tipo.

il genere di cose di cui stiamo parlando è:

trait Base 
trait T1 extends Base // subclassing 
type T2 = Base  // type alias 

Ovviamente non si può usare un tipo alias in sostituzione quando la classe/caratteristica ha un corpo o di informazioni negozi.

Quindi utilizzando tipo alias (T2) piuttosto che si estende con un tratto o una classe (T1) presenta i seguenti vantaggi:

  1. Come si dice sopra, possiamo comporre utilizzando funzioni letterali.
  2. Non produrremo un file .class, ci sarà meno per il compilatore (teoricamente).

Tuttavia, ha i seguenti svantaggi:

  1. essere disponibile nello stesso namespace (package), è necessario definire il tipo di un oggetto del pacchetto, che sarà probabilmente in un altro file da il sito di utilizzo.
  2. Non è possibile saltare "Apri tipo" ctrl-shift-T su un alias in Eclipse, ma è possibile aprire la dichiarazione (F3) in Eclipse. Questo probabilmente verrà risolto in futuro.
  3. Non è possibile utilizzare l'alias di tipo da un'altra lingua, ad esempio Java.
  4. Se l'alias di tipo è parametrizzato, la cancellazione impedisce che la corrispondenza del modello funzioni nello stesso modo che per i tratti.

Il quarto punto è il più grave per me:

trait T1[T] 
trait T2 extends T1[Any] 
type T3 = T1[Any] 

class C2 extends T2 

val c = new C2 
println("" + (c match { case t: T3 => "T3"; case _ => "any" })) 
println("" + (c match { case t: T2 => "T2"; case _ => "any" })) 

Questo produce:

T3 
T2 

Il compilatore dà un avvertimento circa il primo pattern match, che chiaramente non funziona come previsto.

Quindi, finalmente, la domanda. Ci sono altri vantaggi o svantaggi nell'usare gli alias di tipo piuttosto che estendere un tratto/classe?

+2

Il pattern match ha senso per me, anche se ho dovuto pensarci per un minuto. 'T3' essendo un alias di tipo significa che puoi sostituire' T1 [Any] 'per' T3' ovunque. Quindi la prima corrispondenza è in realtà 'c match {cast t: T1 [Any] =>" T3 "; ...} '. 'c' è un' C2', che è un sottotipo di 'T1 [Any]' (tramite 'T2'). Quindi * non * corrispondere al modello sarebbe in realtà più sorprendente. – Ben

+0

@ Ben sono d'accordo che funzioni come specificato, quando ci pensi, ma il mio punto era che non è lo stesso di quando hai un tratto. –

risposta

8

Penso che la chiave sia in realtà quel tipo di alias e tratti sono davvero diverso. L'elenco delle differenze va avanti e avanti e avanti:

  1. sintassi Stenografia lavora per tipo alias (ad esempio x => x+7 potrebbe funzionare come un type I2I = Int => Int) e non tratti.
  2. I caratteri possono contenere dati aggiuntivi, tipi che non possono essere creati dagli alias.
  3. Gli impliciti funzionano per gli alias di tipo ma non per i tratti.
  4. Tratti forniscono tipo di sicurezza/corrispondenza in modo che tipo alias non lo fanno.
  5. Gli alias di tipo hanno regole severe per essere sovrascritti in sottoclassi; i tratti identici hanno invece l'ombra (tutto va bene).

tra gli altri.

Questo perché si sta facendo le cose drammaticamente diverse nei due casi. Digitare gli alias è semplicemente un modo per dire "Ok, quando digito Foo, intendo davvero Bar. Sono uguali. Capito? Dopo averlo fatto, puoi sostituire il nome Foo per Bar ovunque e ogni volta che ne hai voglia. L'unica limitazione è che una volta deciso quale tipo è non è possibile cambiare idea.

Tratti, invece, creare una nuova interfaccia, che può espandere ciò il tratto si estende, o non possono. In caso contrario, è ancora un marcatore che questo è il suo tipo di entità, che può essere abbinato modello, testato per con 'isInstanceOf', e così via.

Quindi, ora che abbiamo stabilito che sono davvero diversi, la domanda è come utilizzarli. E la risposta è piuttosto semplice: se ti piace la classe esistente così com'è, tranne che non ti piace il nome, usa un alias di tipo. Se vuoi creare la tua nuova entità che è distinta da altre cose, usa un tratto (o una sottoclasse). Il primo è per lo più per comodità, mentre il secondo è per sicurezza di tipo aggiunto o capacità. Non credo che una regola che dice "usa uno" invece di "altro" capisca davvero il punto: comprendi le caratteristiche di entrambi e usali quando queste sono le caratteristiche che desideri.

(E poi c'è tipi esistenziali, che forniscono funzionalità simili ai generici ... Ma lasciamo per un'altra domanda.)

+0

Quindi, stai dicendo che i due sono completamente diversi, e uno non dovrebbe seguire il consiglio di Twitter nella loro guida di stile senza una buona conoscenza della differenza di comportamento tra i due? Era più o meno quello che stavo pensando. –

+0

@MatthewFarwell - Sì, questo è un buon riassunto. –

2

Sono diversi dal fatto che un tipo alias definisce il tipo di rapporto di uguaglianza (es. T1 < : T2 & & T1>: T2) mentre l'estensione di trait definisce una stretta relazione di sottotipo (ad esempio T1 <: T2 & &! (T1>: T2)). Usali saggiamente.

Problemi correlati