Tipi di impliciti
Gli impliciti in Scala si riferiscono sia a un valore che può essere passato "automaticamente", per così dire, sia a una conversione da un tipo a un altro effettuata automaticamente.
Conversione implicita
Parlando molto brevemente del secondo tipo, se si chiama un metodo m
su un oggetto o
di una classe C
, e che la classe non lo fa metodo di supporto m
, quindi Scala cercherà una conversione implicita da C
a qualcosa che fa il supporto m
. Un semplice esempio potrebbe essere il metodo map
su String
:
"abc".map(_.toInt)
String
non supporta il metodo map
, ma StringOps
lo fa, e c'è una conversione implicita da String
a StringOps
disposizione (vedi implicit def augmentString
su Predef
).
Parametri impliciti
L'altro tipo di implicito è il parametro implicito . Questi vengono passati alle chiamate di metodo come qualsiasi altro parametro, ma il compilatore tenta di riempirli automaticamente. In caso contrario, si lamenterà. Si possono passare esplicitamente questi parametri, ed è così che si usa breakOut
, per esempio (vedi domanda su breakOut
, in un giorno in cui ti senti in difficoltà).
In questo caso, si deve dichiarare la necessità di un implicito, come la foo
dichiarazione del metodo:
def foo[T](t: T)(implicit integral: Integral[T]) {println(integral)}
Visualizza i limiti
C'è una situazione in cui un implicito è sia una conversione implicita che un parametro implicito. Per esempio:
def getIndex[T, CC](seq: CC, value: T)(implicit conv: CC => Seq[T]) = seq.indexOf(value)
getIndex("abc", 'a')
Il metodo getIndex
può ricevere qualsiasi oggetto, purché sia disponibile una conversione implicita dalla sua classe a Seq[T]
. Per questo motivo, posso passare un String
a getIndex
, e funzionerà.
Dietro le quinte, il compilatore cambia seq.IndexOf(value)
a conv(seq).indexOf(value)
.
Questo è così utile che c'è dello zucchero sintattico per scriverli. Usando questo zucchero sintattico, si getIndex
può definire così:
def getIndex[T, CC <% Seq[T]](seq: CC, value: T) = seq.indexOf(value)
Questo zucchero sintattico è descritto come un limite di vista , simile a un limite superiore ( CC <: Seq[Int]
) o un limite inferiore ( T >: Null
).
Vincoli di contesto
Un altro modello comune nei parametri impliciti è il modello di classe di tipo . Questo modello consente di fornire interfacce comuni alle classi che non le hanno dichiarate. Può fungere sia da modello a ponte - ottenendo la separazione delle preoccupazioni - sia da modello adattatore.
La Integral
classe che hai citato è un classico esempio di modello di classe di tipo. Un altro esempio sulla libreria standard di Scala è Ordering
. C'è una biblioteca che fa ampio uso di questo modello, chiamato Scalaz.
Questo è un esempio del suo utilizzo:
def sum[T](list: List[T])(implicit integral: Integral[T]): T = {
import integral._ // get the implicits in question into scope
list.foldLeft(integral.zero)(_ + _)
}
C'è anche uno zucchero sintattico per esso, chiamato limite di contesto , che è reso meno utile dalla necessità di fare riferimento all'implicito. Una conversione diretta di quel metodo è simile alla seguente:
def sum[T : Integral](list: List[T]): T = {
val integral = implicitly[Integral[T]]
import integral._ // get the implicits in question into scope
list.foldLeft(integral.zero)(_ + _)
}
I limiti di contesto sono più utili quando è sufficiente passarli ad altri metodi che li utilizzano. Ad esempio, il metodo sorted
in Seq
bisogno di un implicito Ordering
. Per creare un metodo reverseSort
, si potrebbe scrivere:
def reverseSort[T : Ordering](seq: Seq[T]) = seq.sorted.reverse
Poiché è Ordering[T]
stato implicitamente passato a reverseSort
, può quindi passarlo implicitamente a sorted
.
Da dove vengono gli impliciti?
Quando il compilatore vede la necessità di un implicito, sia perché stai chiamando un metodo che non esiste sulla classe dell'oggetto, sia perché stai chiamando un metodo che richiede un parametro implicito, cercherà un implicito adatto alla necessità .
Questa ricerca obbedisce a determinate regole che definiscono quali impliciti sono visibili e quali no. La seguente tabella che mostra dove il compilatore cercherà gli impliciti è stata presa da un'eccellente presentazione sugli impliciti di Josh Suereth, che consiglio vivamente a chiunque voglia migliorare la propria conoscenza della Scala. Da allora è stato integrato con feedback e aggiornamenti.
Gli impliciti disponibili sotto il numero 1 di seguito hanno la precedenza su quelli sotto il numero 2. Oltre a ciò, se ci sono diversi argomenti ammissibili che corrispondono al tipo di parametro implicito, verrà scelto uno più specifico usando le regole della risoluzione di sovraccarico statico (vedere Scala Specifica §6.26.3). Informazioni più dettagliate sono disponibili in una domanda a cui mi collego alla fine di questa risposta.
- Primo sguardo nel campo di applicazione attuale
- Impliciti definiti nell'ambito attuale
- Importazioni esplicite
- importazioni di caratteri jolly
Stesso ambito in altri file
- Ora guarda i tipi associati in
- Oggetti compagni di un tipo
- Ambito implicito del tipo di argomento (2.9.1)
- Ambito implicito degli argomenti di tipo (2.8.0)
- Oggetti esterni per tipi nidificati
- Altre dimensioni
Diamo alcuni esempi per loro:
Impliciti definiti nell'ambito corrente
implicit val n: Int = 5
def add(x: Int)(implicit y: Int) = x + y
add(5) // takes n from the current scope
Importazioni esplicite
import scala.collection.JavaConversions.mapAsScalaMap
def env = System.getenv() // Java map
val term = env("TERM") // implicit conversion from Java Map to Scala Map
Importazioni con caratteri jolly
def sum[T : Integral](list: List[T]): T = {
val integral = implicitly[Integral[T]]
import integral._ // get the implicits in question into scope
list.foldLeft(integral.zero)(_ + _)
}
Stesso ambito in altri file
Modifica : sembra che questo non abbia una precedenza diversa. Se hai qualche esempio che dimostra una distinzione di precedenza, fai un commento. Altrimenti, non fare affidamento su questo.
Questo è come il primo esempio, ma supponendo che la definizione implicita sia in un file diverso rispetto al suo utilizzo. Vedi anche come gli oggetti pacchetto potrebbero essere utilizzati per portare impliciti.
Oggetti compagni di un tipo
Ci sono due compagni oggetto di nota qui. Innanzitutto, viene esaminato l'oggetto compagno del tipo "sorgente". Ad esempio, all'interno dell'oggetto Option
c'è una conversione implicita in Iterable
, quindi si può chiamare Iterable
metodi Option
o passare Option
a qualcosa che si aspetta un Iterable
. Per esempio:
for {
x <- List(1, 2, 3)
y <- Some('x')
} yield (x, y)
Tale espressione viene tradotta dal compilatore in
List(1, 2, 3).flatMap(x => Some('x').map(y => (x, y)))
Tuttavia, si List.flatMap
aspetta un TraversableOnce
, che Option
non lo è. Il compilatore quindi guarda all'interno Option
del compagno oggetto e trova la conversione in Iterable
, che è a TraversableOnce
, rendendo questa espressione corretta.
In secondo luogo, l'oggetto associato del tipo previsto:
List(1, 2, 3).sorted
Il metodo sorted
prende un implicito Ordering
. In questo caso, guarda all'interno dell'oggetto Ordering
, compagno della classe Ordering
e trova un implicito Ordering[Int]
lì.
Si noti che vengono esaminati anche gli oggetti companion di superclasse. Per esempio:
class A(val n: Int)
object A {
implicit def str(a: A) = "A: %d" format a.n
}
class B(val x: Int, y: Int) extends A(y)
val b = new B(5, 2)
val s: String = b // s == "A: 2"
È così che Scala ha trovato l'implicito Numeric[Int]
e Numeric[Long]
nella tua domanda, a proposito, come si trovano all'interno Numeric
, no Integral
.
Ambito implicito del tipo di argomento
Se si dispone di un metodo con un tipo di argomento A
, A
verrà considerato anche l'ambito implicito del tipo . Per "ambito implicito" intendo che tutte queste regole saranno applicate in modo ricorsivo - per esempio, l'oggetto associato A
sarà cercato in modo implicito, come da regola sopra.
Si noti che ciò non significa che l'ambito implicito di A
verrà cercato per le conversioni di quel parametro, ma dell'intera espressione. Per esempio:
class A(val n: Int) {
def +(other: A) = new A(n + other.n)
}
object A {
implicit def fromInt(n: Int) = new A(n)
}
// This becomes possible:
1 + new A(1)
// because it is converted into this:
A.fromInt(1) + new A(1)
Questo è disponibile da Scala 2.9.1.
Ambito implicito di argomenti di tipo
Ciò è necessario per far sì che il modello di classe di tipo funzioni davvero. Considera Ordering
, ad esempio: viene fornito con alcuni impliciti nel suo oggetto associato, ma non puoi aggiungere elementi ad esso. Quindi, come puoi crearne uno Ordering
per la tua classe che si trova automaticamente?
Cominciamo con l'implementazione:
class A(val n: Int)
object A {
implicit val ord = new Ordering[A] {
def compare(x: A, y: A) = implicitly[Ordering[Int]].compare(x.n, y.n)
}
}
Quindi, considera cosa succede quando chiami
List(new A(5), new A(2)).sorted
Come abbiamo visto, il metodo sorted
prevede un Ordering[A]
(in realtà, prevede un Ordering[B]
, dove B >: A
). Non c'è nulla di simile all'interno Ordering
e non esiste un tipo "sorgente" su cui guardare. Ovviamente, lo sta trovando all'interno A
, che è un argomento tipo di Ordering
.
Questo è anche il modo in cui funzionano diversi metodi di raccolta in attesa CanBuildFrom
: gli impliciti si trovano all'interno degli oggetti associati ai parametri di tipo di CanBuildFrom
.
Nota : Ordering
è definito come trait Ordering[T]
, dove T
è un parametro di tipo. In precedenza, avevo detto che Scala guardava all'interno dei parametri di tipo, il che non ha molto senso. L'implicito cercato sopra è Ordering[A]
, dove A
è un tipo reale, non un parametro di tipo: è un argomento di tipo a Ordering
. Vedere la sezione 7.2 delle specifiche Scala.
Questo è disponibile da Scala 2.8.0.
Oggetti esterni per tipi nidificati
In realtà non ho visto esempi di questo. Le sarei grato se qualcuno potesse condividerne uno. Il principio è semplice:
class A(val n: Int) {
class B(val m: Int) { require(m < n) }
}
object A {
implicit def bToString(b: A#B) = "B: %d" format b.m
}
val a = new A(5)
val b = new a.B(3)
val s: String = b // s == "B: 3"
Altre dimensioni
Sono abbastanza sicuro che fosse uno scherzo, ma questa risposta potrebbe non essere aggiornata. Quindi non prendere questa domanda come l'arbitro finale di ciò che sta accadendo, e se noti che è diventato obsoleto, ti prego di informarmi in modo che io possa risolverlo.
MODIFICARE
Domande correlate di interesse: