2016-03-09 18 views
38
codice

Navigazione informe, mi sono imbattuto in questo apparentemente estranea {}here e here:Cosa significa `T {}` fare Scala

trait Witness extends Serializable { 
    type T 
    val value: T {} 
} 

trait SingletonOps { 
    import record._ 
    type T 
    def narrow: T {} = witness.value 
} 

ho quasi ignorato come un errore di battitura in quanto non fa altro che a quanto pare lo fa qualcosa. Vedi questo commit: https://github.com/milessabin/shapeless/commit/56a3de48094e691d56a937ccf461d808de391961

Non ho idea di cosa faccia. Qualcuno può spiegare?

+1

Cosa significa "widen" nel messaggio di commit: "Apparentemente un raffinamento vuoto significa" non allargarmi ".? –

+0

L'allargamento si riferisce all'amplificazione implicita del tipo (ad es., List (1, 2.0) è una lista [Double] perché l'int 1 può essere allargato a raddoppiare per corrispondere a 2.0). Questo elenco potrebbe aiutare a spiegarlo: https://gist.github.com/milessabin/65fa0d4ef373781d3ab4 –

+0

Widen significa allargare il tipo di 'oggetto X' dal tipo singleton' X.type', poiché di solito si desidera evitare di inferire i tipi di singleton, ma qui il tipo singleton è desiderato. –

risposta

51

Qualsiasi tipo può essere seguito da una sequenza di tipo {} chiusa e da definizioni di membri non di tipo astratto. Questo è noto come "perfezionamento" e viene utilizzato per fornire ulteriore precisione sul tipo di base che viene rifinito. In pratica i perfezionamenti sono più comunemente usati per esprimere i vincoli sui membri di tipo astratto del tipo che viene raffinato.

È un fatto poco noto che questa sequenza è permesso di essere vuoto, e nella forma che si può vedere nel codice sorgente informe, T {} è il tipo T con una raffinatezza vuoto. Qualsiasi refinement vuoto è ... vuoto ... quindi non aggiunge alcun vincolo aggiuntivo al tipo raffinato e quindi i tipi T e T {} sono equivalenti. Siamo in grado di ottenere il compilatore Scala per verificare che per noi in questo modo,

scala> implicitly[Int =:= Int {}] 
res0: =:=[Int,Int] = <function1> 

Quindi, perché devo fare una cosa così apparentemente inutile in informe? È a causa dell'interazione tra la presenza di raffinamenti e l'inferenza di tipo. Se si guarda in the relevant section della Specifica lingua di Scala, si noterà che l'algoritmo di inferenza del tipo tenta di evitare di inferire i tipi di singleton in almeno alcune circostanze. Ecco un esempio di esso fare proprio questo,

scala> class Foo ; val foo = new Foo 
defined class Foo 
foo: Foo = [email protected] 

scala> val f1 = foo 
f1: Foo = [email protected] 

scala> val f2: foo.type = foo 
f2: foo.type = [email protected] 

Come si può vedere dalla definizione di f2 compilatore Scala sa che il valore foo ha il tipo più preciso foo.type (es. Il tipo Singleton di val foo), tuttavia, se non richiesto esplicitamente, non ne dedurrà quel tipo più preciso. Invece deduce il tipo non singleton (cioè allargato) Foo come si può vedere nel caso di f1.

Ma nel caso di Witness in informe I esplicitamente voglio tipo singleton deve dedurre per usi dell'organo value (il punto di Witness è ci permettono di passare tra i livelli di tipo e valore tramite tipi singoletto) , quindi c'è un modo in cui il compilatore di Scala può essere persuaso a farlo?

Si scopre che una raffinatezza vuoto fa esattamente questo,

scala> def narrow[T <: AnyRef](t: T): t.type = t 
narrow: [T <: AnyRef](t: T)t.type 

scala> val s1 = narrow("foo") // Widened 
s1: String = foo 

scala> def narrow[T <: AnyRef](t: T): t.type {} = t // Note empty refinement 
narrow: [T <: AnyRef](t: T)t.type 

scala> val s2 = narrow("foo") // Not widened 
s2: String("foo") = foo 

Come si può vedere in quanto sopra trascrizione REPL, nel primo caso s1 è stato tipizzato come tipo allargato String mentre s2 è stato assegnato il tipo singleton String("foo").

È richiesto da SLS? No, ma è coerente con esso, e ha un qualche senso. Gran parte della meccanica di inferenza di tipo di Scala è definita dall'implementazione piuttosto che specicata e questo è probabilmente uno dei casi meno sorprendenti e problematici di ciò.

+0

Primo, i tipi equivalenti dovrebbero comportarsi in modo identico e dovrebbero in effetti avere la stessa rappresentazione, quindi io trova questo sorprendente e cattivo. Ma sono confuso dal comportamento: immagino che l'ampliamento venga applicato a "" foo ".type {}" e non riesce ad ampliarlo, è così? Si potrebbe nascondere 'T {}' dietro un'annotazione macro 'T @ narrow'? – Blaisorblade

+0

Sì, è vero. Non vedo davvero alcun motivo per pensare che un'annotazione macro sia superiore all'utilizzo di una sintassi esistente, ma altrimenti inutilizzata. –

+0

Grazie per l'ottima spiegazione @MilesSabin – pathikrit

Problemi correlati