Quali sono i contesti Scala e i limiti di visualizzazione?


267

In modo semplice, quali sono i limiti del contesto e della vista e qual è la differenza tra loro?

Anche alcuni esempi facili da seguire sarebbero fantastici!

Risposte:


477

Pensavo che questo fosse già stato posto, ma, in tal caso, la domanda non è evidente nella barra "correlata". Quindi, eccolo qui:

Che cos'è una vista vincolata?

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, Adovrebbe avere una conversione implicita in Bdisponibile, in modo che si possano chiamare Bmetodi 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 Orderedquesto:

def f[A <% Ordered[A]](a: A, b: A) = if (a < b) a else b

Perché uno può convertire Ain 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.

Che cos'è un contesto associato?

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 Arrayinizializzazione su un tipo parametrico richiede ClassManifestdi 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, implicitlyviene 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.

Come vengono implementati i limiti di visualizzazione e limiti di contesto?

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)

A cosa servono i View View?

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 Intnon è 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 fnon è necessario conoscerlo.

Inoltre Ordered, l'uso più comune dalla libreria è la gestione Stringe 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 Stringsarebbe 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

A cosa servono i limiti di contesto?

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 Orderedtutta 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 Orderingche 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' ClassManifestuso, 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:


9
Grazie mille. So che è stata data una risposta prima, e forse non ho letto abbastanza attentamente allora, ma la tua spiegazione qui è la più chiara che abbia mai visto. Quindi grazie ancora.
Chrsan,

3
@chrsan Ho aggiunto altre due sezioni, andando più in dettaglio su dove si usa ciascuna.
Daniel C. Sobral,

2
Penso che questa sia una spiegazione eccellente. Vorrei tradurre questo per il mio blog tedesco (dgronau.wordpress.com) se per te va bene.
Landei,

3
Questa è di gran lunga la spiegazione migliore e più completa di questo argomento che ho trovato finora. Molte grazie!
fotNelton

2
Sooo, quando uscirà il tuo libro alla Scala, e dove posso acquistarlo :)
wfbarksdale,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.