Risposte:
Pensavo che questo fosse già stato posto, ma, in tal caso, la domanda non è evidente nella barra "correlata". Quindi, eccolo qui:
Un limite di vista era un meccanismo introdotto in Scala per consentire l'uso di un tipo A
come se fosse un tipo B
. La sintassi tipica è questa:
def f[A <% B](a: A) = a.bMethod
In altre parole, A
dovrebbe avere una conversione implicita in B
disponibile, in modo che si possano chiamare B
metodi su un oggetto di tipo A
. L'uso più comune dei limiti di vista nella libreria standard (prima di Scala 2.8.0, comunque), è con Ordered
questo:
def f[A <% Ordered[A]](a: A, b: A) = if (a < b) a else b
Perché uno può convertire A
in un Ordered[A]
, e poiché Ordered[A]
definisce il metodo <(other: A): Boolean
, posso usare l'espressione a < b
.
Si noti che i limiti di visualizzazione sono obsoleti , è necessario evitarli.
I limiti di contesto sono stati introdotti in Scala 2.8.0 e vengono generalmente utilizzati con il cosiddetto modello di classe di tipo , un modello di codice che emula la funzionalità fornita dalle classi di tipo Haskell, sebbene in modo più dettagliato.
Mentre un limite di vista può essere utilizzato con tipi semplici (ad esempio, A <% String
), un limite di contesto richiede un tipo con parametri , come Ordered[A]
sopra, ma diversamente String
.
Un limite di contesto descrive un valore implicito , anziché visualizzare la conversione implicita del limite . Viene utilizzato per dichiarare che per alcuni tipi A
è disponibile un valore implicito di tipo B[A]
. La sintassi è la seguente:
def f[A : B](a: A) = g(a) // where g requires an implicit value of type B[A]
Questo è più confuso della vista associata perché non è immediatamente chiaro come usarla. L'esempio comune di utilizzo in Scala è questo:
def f[A : ClassManifest](n: Int) = new Array[A](n)
Un Array
inizializzazione su un tipo parametrico richiede ClassManifest
di essere disponibile, per motivi arcani legati alla cancellazione di tipo e la natura non cancellazione degli array.
Un altro esempio molto comune nella libreria è un po 'più complesso:
def f[A : Ordering](a: A, b: A) = implicitly[Ordering[A]].compare(a, b)
Qui, implicitly
viene utilizzato per recuperare il valore implicito che vogliamo, uno di tipo Ordering[A]
, quale classe definisce il metodo compare(a: A, b: A): Int
.
Vedremo un altro modo di farlo di seguito.
Non dovrebbe sorprendere che sia i limiti di visualizzazione sia i limiti di contesto siano implementati con parametri impliciti, data la loro definizione. In realtà, la sintassi che ho mostrato sono zuccheri sintattici per ciò che realmente accade. Vedi sotto come depolverano:
def f[A <% B](a: A) = a.bMethod
def f[A](a: A)(implicit ev: A => B) = a.bMethod
def g[A : B](a: A) = h(a)
def g[A](a: A)(implicit ev: B[A]) = h(a)
Quindi, naturalmente, è possibile scriverli nella loro sintassi completa, che è particolarmente utile per i limiti di contesto:
def f[A](a: A, b: A)(implicit ord: Ordering[A]) = ord.compare(a, b)
I limiti di visualizzazione vengono utilizzati principalmente per sfruttare il modello pimp my library , attraverso il quale si "aggiungono" metodi a una classe esistente, in situazioni in cui si desidera restituire in qualche modo il tipo originale. Se non è necessario restituire quel tipo in alcun modo, non è necessario un limite di vista.
Il classico esempio di utilizzo associato alla vista è la gestione Ordered
. Si noti che Int
non è Ordered
, per esempio, se non v'è una conversione implicita. L'esempio fornito in precedenza richiede un limite di vista perché restituisce il tipo non convertito:
def f[A <% Ordered[A]](a: A, b: A): A = if (a < b) a else b
Questo esempio non funzionerà senza limiti di visualizzazione. Tuttavia, se dovessi restituire un altro tipo, non avrei più bisogno di un limite di vista:
def f[A](a: Ordered[A], b: A): Boolean = a < b
La conversione qui (se necessario) avviene prima di passare il parametro a f
, quindi f
non è necessario conoscerlo.
Inoltre Ordered
, l'uso più comune dalla libreria è la gestione String
e Array
, che sono classi Java, come se fossero raccolte Scala. Per esempio:
def f[CC <% Traversable[_]](a: CC, b: CC): CC = if (a.size < b.size) a else b
Se si provasse a farlo senza limiti di vista, il tipo restituito di a String
sarebbe a WrappedString
(Scala 2.8), e allo stesso modo per Array
.
La stessa cosa accade anche se il tipo viene utilizzato solo come parametro di tipo del tipo restituito:
def f[A <% Ordered[A]](xs: A*): Seq[A] = xs.toSeq.sorted
I limiti di contesto vengono utilizzati principalmente in quello che è diventato noto come modello di typeclass , come riferimento alle classi di tipi di Haskell. Fondamentalmente, questo modello implementa un'alternativa all'ereditarietà rendendo disponibile la funzionalità attraverso una sorta di modello adattatore implicito.
L'esempio classico è quello di Scala 2.8 Ordering
, che è stato sostituito in Ordered
tutta la biblioteca di Scala. L'utilizzo è:
def f[A : Ordering](a: A, b: A) = if (implicitly[Ordering[A]].lt(a, b)) a else b
Anche se di solito lo vedrai scritto in questo modo:
def f[A](a: A, b: A)(implicit ord: Ordering[A]) = {
import ord.mkOrderingOps
if (a < b) a else b
}
Che sfruttano alcune conversioni implicite all'interno Ordering
che consentono lo stile tradizionale dell'operatore. Un altro esempio in Scala 2.8 è il Numeric
:
def f[A : Numeric](a: A, b: A) = implicitly[Numeric[A]].plus(a, b)
Un esempio più complesso è l'utilizzo della nuova raccolta di CanBuildFrom
, ma c'è già una risposta molto lunga a riguardo, quindi lo eviterò qui. E, come accennato in precedenza, c'è l' ClassManifest
uso, necessario per inizializzare nuovi array senza tipi concreti.
Il contesto associato al modello della typeclass ha molte più probabilità di essere utilizzato dalle tue classi, in quanto consentono la separazione delle preoccupazioni, mentre i limiti della vista possono essere evitati nel tuo codice con una buona progettazione (è usato principalmente per aggirare il design di qualcun altro ).
Sebbene sia stato possibile per molto tempo, l'uso dei limiti di contesto è davvero decollato nel 2010, e ora si trova in una certa misura nella maggior parte delle librerie e dei quadri più importanti di Scala. L'esempio più estremo del suo utilizzo, tuttavia, è la libreria Scalaz, che porta molto potere di Haskell alla Scala. Raccomando di leggere i modelli di tipeclass per conoscere meglio tutti i modi in cui può essere utilizzato.
MODIFICARE
Domande correlate di interesse: