¿Qué es un "contexto limitado" en Scala?

115

Una de las nuevas características de Scala 2.8 son los límites de contexto. ¿Qué es un contexto limitado y dónde es útil?

Por supuesto, busqué primero (y encontré, por ejemplo, esto ) pero no pude encontrar ninguna información realmente clara y detallada.

Jesper
fuente
8
También mira esto para un recorrido por todos los tipos de límites: gist.github.com/257758/47f06f2f3ca47702b3a86c76a5479d096cb8c7ec
Arjan Blokzijl
2
Esta excelente respuesta compara / contrasta los límites del contexto y los límites de la vista: stackoverflow.com/questions/4465948/…
Aaron Novstrup
Esta es una respuesta muy agradable stackoverflow.com/a/25250693/1586965
samthebest

Respuestas:

107

¿Encontraste este artículo ? Cubre la nueva función vinculada al contexto, dentro del contexto de las mejoras de la matriz.

Generalmente, un parámetro de tipo con un límite de contexto tiene la forma [T: Bound]; se expande a un parámetro de tipo plano Tjunto con un parámetro implícito de tipo Bound[T].

Considere el método tabulateque forma una matriz a partir de los resultados de aplicar una función f dada en un rango de números desde 0 hasta una longitud determinada. Hasta Scala 2.7, el tabulado se podría escribir de la siguiente manera:

def tabulate[T](len: Int, f: Int => T) = {
    val xs = new Array[T](len)
    for (i <- 0 until len) xs(i) = f(i)
    xs
}

En Scala 2.8 esto ya no es posible, porque la información en tiempo de ejecución es necesaria para crear la representación correcta de Array[T]. Es necesario proporcionar esta información pasando un ClassManifest[T]en el método como un parámetro implícito:

def tabulate[T](len: Int, f: Int => T)(implicit m: ClassManifest[T]) = {
    val xs = new Array[T](len)
    for (i <- 0 until len) xs(i) = f(i)
    xs
}

Como forma abreviada, se puede usar un enlace de contexto en el parámetro de tipo T, dando:

def tabulate[T: ClassManifest](len: Int, f: Int => T) = {
    val xs = new Array[T](len)
    for (i <- 0 until len) xs(i) = f(i)
    xs
}
Robert Harvey
fuente
145

La respuesta de Robert cubre los detalles técnicos de Context Bounds. Te daré mi interpretación de su significado.

En Scala, un View Bound ( A <% B) captura el concepto de 'se puede ver como' (mientras que un límite superior <:captura el concepto de 'es un'). Un límite de contexto ( A : C) dice 'tiene un' sobre un tipo. Puede leer los ejemplos sobre manifiestos como " Ttiene un Manifest". El ejemplo al que vinculó acerca de Orderedvs Orderingilustra la diferencia. Un método

def example[T <% Ordered[T]](param: T)

dice que el parámetro puede verse como un Ordered. Comparar con

def example[T : Ordering](param: T)

que dice que el parámetro tiene asociado Ordering.

En términos de uso, tomó un tiempo establecer las convenciones, pero se prefieren los límites de contexto a los límites de vista (los límites de vista ahora están en desuso ). Una sugerencia es que se prefiere un enlace de contexto cuando se necesita transferir una definición implícita de un alcance a otro sin necesidad de hacer referencia a él directamente (este es ciertamente el caso de los ClassManifestutilizados para crear una matriz).

Otra forma de pensar sobre los límites de la vista y los límites del contexto es que la primera transfiere conversiones implícitas desde el alcance del llamador. El segundo transfiere objetos implícitos del alcance del llamador.

Ben Lings
fuente
2
"tiene un" en lugar de "es un" o "visto como" fue la idea clave para mí, que no se ve en ninguna otra explicación. Tener una versión en inglés simple de los operadores / funciones, de otro modo ligeramente crípticos, hace que sea mucho más fácil de asimilar, ¡gracias!
DNA
1
@Ben Lings ¿Qué quieres decir con .... 'tiene un' sobre un tipo ...? ¿Qué es un tipo ?
jhegedus
1
@jhegedus Aquí está mi análisis: "sobre un tipo" significa que A se refiere a un tipo. La frase "tiene una" se utiliza a menudo en el diseño orientado a objetos para describir las relaciones entre objetos (por ejemplo, el cliente "tiene una" dirección). Pero aquí la relación "tiene una" es entre tipos, no entre objetos. Es una analogía vaga porque la relación "tiene una" no es inherente o universal como es en el diseño OO; un cliente siempre tiene una dirección, pero para el contexto enlazado, una A no siempre tiene una C. Más bien, el contexto enlazado especifica que una instancia de C [A] debe proporcionarse implícitamente.
jbyler
¡He estado aprendiendo Scala durante un mes, y esta es la mejor explicación que he visto en este mes! ¡Gracias @Ben!
Lifu Huang
@Ben Lings: Gracias, después de pasar tanto tiempo para comprender lo que está ligado al contexto, su respuesta es muy útil. [ has aTiene más sentido para mí]
Shankar
39

(Esta es una nota entre paréntesis. Lea y comprenda primero las otras respuestas).

Los límites de contexto en realidad generalizan los límites de vista.

Entonces, dado este código expresado con un View Bound:

scala> implicit def int2str(i: Int): String = i.toString
int2str: (i: Int)String

scala> def f1[T <% String](t: T) = 0
f1: [T](t: T)(implicit evidence$1: (T) => String)Int

Esto también podría expresarse con un Context Bound, con la ayuda de un alias de tipo que represente funciones de un tipo Fa otro T.

scala> trait To[T] { type From[F] = F => T }           
defined trait To

scala> def f2[T : To[String]#From](t: T) = 0       
f2: [T](t: T)(implicit evidence$1: (T) => java.lang.String)Int

scala> f2(1)
res1: Int = 0

Un límite de contexto debe usarse con un constructor de tipo de tipo * => *. Sin embargo, el constructor Function1de tipos es de su tipo (*, *) => *. El uso del alias de tipo aplica parcialmente el segundo parámetro de tipo con el tipo String, produciendo un constructor de tipo del tipo correcto para su uso como un límite de contexto.

Existe una propuesta que le permite expresar directamente tipos aplicados parcialmente en Scala, sin el uso del alias de tipo dentro de un rasgo. Entonces podrías escribir:

def f3[T : [X](X => String)](t: T) = 0 
retrónimo
fuente
¿Podría explicar el significado de #From en la definición de f2? No estoy seguro de dónde se está construyendo el tipo F (¿dije esto correctamente?)
Collin
1
Se llama proyección de tipo, y hace referencia a un miembro Fromde tipo del tipo To[String]. No proporcionamos un argumento de tipo a From, por lo que nos referimos al constructor de tipos, no a un tipo. Este constructor de tipos es del tipo adecuado para ser utilizado como un límite de contexto - * -> *. Esto limita el parámetro de tipo Tal requerir un parámetro implícito de tipo To[String]#From[T]. Expanda los alias de tipo y listo, se queda Function1[String, T].
retronym
¿Debería ser Function1 [T, String]?
ssanj
18

Esta es otra nota entre paréntesis.

Como señaló Ben , un límite de contexto representa una restricción "tiene-a" entre un parámetro de tipo y una clase de tipo. Dicho de otra manera, representa una restricción de que existe un valor implícito de una clase de tipo particular.

Cuando se utiliza un límite de contexto, a menudo es necesario resaltar ese valor implícito. Por ejemplo, dada la restricción T : Ordering, a menudo se necesitará la instancia de Ordering[T]que satisfaga la restricción. Como se demuestra aquí , es posible acceder al valor implícito mediante el implicitlymétodo o un método un poco más útil context:

def **[T : Numeric](xs: Iterable[T], ys: Iterable[T]) = 
   xs zip ys map { t => implicitly[Numeric[T]].times(t._1, t._2) }

o

def **[T : Numeric](xs: Iterable[T], ys: Iterable[T]) =
   xs zip ys map { t => context[T]().times(t._1, t._2) }
Aaron Novstrup
fuente