Tipos de implicidades
Implicits en Scala se refiere a un valor que se puede pasar "automáticamente", por así decirlo, o una conversión de un tipo a otro que se realiza automáticamente.
Conversión Implícita
Hablando muy brevemente sobre el último tipo, si uno llama a un método m
en un objeto o
de una clase C
, y esa clase no admite el método m
, entonces Scala buscará una conversión implícita de C
algo que sí lo admite m
. Un ejemplo simple sería el método map
en String
:
"abc".map(_.toInt)
String
no soporta el método map
, pero StringOps
lo hace, y hay una conversión implícita de String
que StringOps
dispone (véase implicit def augmentString
el Predef
).
Parámetros implícitos
El otro tipo de implícito es el parámetro implícito . Estos se pasan a las llamadas a métodos como cualquier otro parámetro, pero el compilador intenta completarlos automáticamente. Si no puede, se quejará. Uno puede pasar estos parámetros explícitamente, que es cómo se usa breakOut
, por ejemplo (vea la pregunta sobre breakOut
, en un día en que se siente preparado para un desafío).
En este caso, uno tiene que declarar la necesidad de un implícito, como la foo
declaración del método:
def foo[T](t: T)(implicit integral: Integral[T]) {println(integral)}
Ver límites
Hay una situación en la que un implícito es tanto una conversión implícita como un parámetro implícito. Por ejemplo:
def getIndex[T, CC](seq: CC, value: T)(implicit conv: CC => Seq[T]) = seq.indexOf(value)
getIndex("abc", 'a')
El método getIndex
puede recibir cualquier objeto, siempre que haya una conversión implícita disponible de su clase a Seq[T]
. Por eso, puedo pasar un String
a getIndex
, y funcionará.
Detrás de escena, el compilador cambia seq.IndexOf(value)
a conv(seq).indexOf(value)
.
Esto es tan útil que hay azúcar sintáctico para escribirlos. Usando este azúcar sintáctico, getIndex
se puede definir así:
def getIndex[T, CC <% Seq[T]](seq: CC, value: T) = seq.indexOf(value)
Este azúcar sintáctico se describe como un límite de vista , similar a un límite superior ( CC <: Seq[Int]
) o un límite inferior ( T >: Null
).
Límites de contexto
Otro patrón común en los parámetros implícitos es el patrón de clase de tipo . Este patrón permite la provisión de interfaces comunes a clases que no las declararon. Puede servir como un patrón de puente, obteniendo una separación de las preocupaciones, y como un patrón adaptador.
La Integral
clase que mencionó es un ejemplo clásico de patrón de clase de tipo. Otro ejemplo en la biblioteca estándar de Scala es Ordering
. Hay una biblioteca que hace un uso intensivo de este patrón, llamado Scalaz.
Este es un ejemplo de su uso:
def sum[T](list: List[T])(implicit integral: Integral[T]): T = {
import integral._ // get the implicits in question into scope
list.foldLeft(integral.zero)(_ + _)
}
También hay azúcar sintáctico para ello, llamado un contexto vinculado , que se hace menos útil por la necesidad de referirse a lo implícito. Una conversión directa de ese método se ve así:
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)(_ + _)
}
Los límites de contexto son más útiles cuando solo necesita pasarlos a otros métodos que los usan. Por ejemplo, el método sorted
en Seq
necesita una implícita Ordering
. Para crear un método reverseSort
, se podría escribir:
def reverseSort[T : Ordering](seq: Seq[T]) = seq.sorted.reverse
Debido a que Ordering[T]
se pasó implícitamente a reverseSort
, puede pasarlo implícitamente a sorted
.
¿De dónde vienen los Implicits?
Cuando el compilador ve la necesidad de un implícito, ya sea porque está llamando a un método que no existe en la clase del objeto o porque está llamando a un método que requiere un parámetro implícito, buscará un implícito que se ajuste a la necesidad .
Esta búsqueda obedece ciertas reglas que definen qué implicaciones son visibles y cuáles no. La siguiente tabla que muestra dónde buscará implicidades el compilador se tomó de una excelente presentación sobre las implicidades de Josh Suereth, que recomiendo de todo corazón a cualquiera que quiera mejorar sus conocimientos de Scala. Se ha complementado desde entonces con comentarios y actualizaciones.
Las implicaciones disponibles bajo el número 1 a continuación tienen prioridad sobre las que están bajo el número 2. Aparte de eso, si hay varios argumentos elegibles que coinciden con el tipo de parámetro implícito, se elegirá uno más específico utilizando las reglas de resolución de sobrecarga estática (ver Scala Especificación §6.26.3). Se puede encontrar información más detallada en una pregunta que enlace al final de esta respuesta.
- Primer vistazo en el alcance actual
- Implicits definidos en el alcance actual
- Importaciones explícitas
- importaciones de comodines
Mismo alcance en otros archivos
- Ahora mire los tipos asociados en
- Objetos complementarios de un tipo
- Alcance implícito del tipo de argumento (2.9.1)
- Alcance implícito de los argumentos de tipo (2.8.0)
- Objetos externos para tipos anidados
- Otras dimensiones
Demos algunos ejemplos para ellos:
Implicitos definidos en el alcance actual
implicit val n: Int = 5
def add(x: Int)(implicit y: Int) = x + y
add(5) // takes n from the current scope
Importaciones explícitas
import scala.collection.JavaConversions.mapAsScalaMap
def env = System.getenv() // Java map
val term = env("TERM") // implicit conversion from Java Map to Scala Map
Importaciones de comodines
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)(_ + _)
}
Mismo alcance en otros archivos
Editar : Parece que esto no tiene una precedencia diferente. Si tiene algún ejemplo que demuestre una distinción de precedencia, haga un comentario. De lo contrario, no confíes en este.
Esto es como el primer ejemplo, pero suponiendo que la definición implícita esté en un archivo diferente de su uso. Vea también cómo se pueden usar los objetos del paquete para generar implicidades.
Objetos complementarios de un tipo
Hay dos compañeros de objeto de nota aquí. Primero, se analiza el objeto compañero del tipo "fuente". Por ejemplo, dentro del objeto Option
hay una conversión implícita a Iterable
, por lo que uno puede invocar Iterable
métodos Option
o pasar Option
a algo que espera un Iterable
. Por ejemplo:
for {
x <- List(1, 2, 3)
y <- Some('x')
} yield (x, y)
El compilador traduce esa expresión a
List(1, 2, 3).flatMap(x => Some('x').map(y => (x, y)))
Sin embargo, List.flatMap
espera un TraversableOnce
, que Option
no lo es. El compilador luego mira dentro Option
del objeto compañero y encuentra la conversión a Iterable
, que es a TraversableOnce
, haciendo correcta esta expresión.
En segundo lugar, el objeto complementario del tipo esperado:
List(1, 2, 3).sorted
El método sorted
tiene un implícito Ordering
. En este caso, mira dentro del objeto Ordering
, compañero de la clase Ordering
, y encuentra un implícito Ordering[Int]
allí.
Tenga en cuenta que también se examinan los objetos complementarios de las superclases. Por ejemplo:
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"
Así es como Scala encontró lo implícito Numeric[Int]
y Numeric[Long]
en su pregunta, por cierto, ya que se encuentran dentro Numeric
, no Integral
.
Alcance implícito del tipo de argumento
Si tiene un método con un tipo de argumento A
, A
también se considerará el alcance implícito del tipo . Por "alcance implícito" quiero decir que todas estas reglas se aplicarán de forma recursiva; por ejemplo, A
se buscará implicidades en el objeto complementario de acuerdo con la regla anterior.
Tenga en cuenta que esto no significa A
que se buscará en el alcance implícito de las conversiones de ese parámetro, sino de toda la expresión. Por ejemplo:
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)
Esto está disponible desde Scala 2.9.1.
Alcance implícito de los argumentos de tipo
Esto es necesario para que el patrón de clase de tipo realmente funcione. Considere Ordering
, por ejemplo: viene con algunas implicidades en su objeto complementario, pero no puede agregarle cosas. Entonces, ¿cómo puede hacer una Ordering
para su propia clase que se encuentra automáticamente?
Comencemos con la implementación:
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)
}
}
Entonces, considera lo que sucede cuando llamas
List(new A(5), new A(2)).sorted
Como vimos, el método sorted
espera un Ordering[A]
(en realidad, espera un Ordering[B]
, donde B >: A
). No hay tal cosa dentro Ordering
, y no hay ningún tipo de "fuente" en el que mirar. Obviamente, lo está encontrando dentro A
, que es un argumento tipo de Ordering
.
Así es también como varios métodos de recolección esperan CanBuildFrom
trabajo: las implicaciones se encuentran dentro de los objetos complementarios a los parámetros de tipo CanBuildFrom
.
Nota : Ordering
se define como trait Ordering[T]
, donde T
es un parámetro de tipo. Anteriormente, dije que Scala miró dentro de los parámetros de tipo, lo que no tiene mucho sentido. El implícito buscado anteriormente es Ordering[A]
, donde A
es un tipo real, no un parámetro de tipo: es un argumento de tipo para Ordering
. Consulte la sección 7.2 de la especificación Scala.
Está disponible desde Scala 2.8.0.
Objetos externos para tipos anidados
En realidad no he visto ejemplos de esto. Estaría agradecido si alguien pudiera compartir uno. El principio es simple:
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"
Otras dimensiones
Estoy bastante seguro de que fue una broma, pero es posible que esta respuesta no esté actualizada. Por lo tanto, no tome esta pregunta como el árbitro final de lo que está sucediendo, y si nota que se ha desactualizado, infórmeme para que pueda solucionarlo.
EDITAR
Preguntas relacionadas de interés:
Quería averiguar la precedencia de la resolución implícita de parámetros, no solo dónde busca, así que escribí una publicación de blog revisando implicidades sin impuestos de importación (y la precedencia implícita de parámetros nuevamente después de algunos comentarios).
Aquí está la lista:
Si en cualquier etapa encontramos más de una regla de sobrecarga estática implícita, se utiliza para resolverla.
fuente