De una manera simple, ¿qué son los límites de contexto y vista y cuál es la diferencia entre ellos?
¡Algunos ejemplos fáciles de seguir también serían geniales!
Pensé que esto ya se había preguntado, pero, de ser así, la pregunta no es aparente en la barra "relacionada". Asi que aqui esta:
Una vista enlazada era un mecanismo introducido en Scala para permitir el uso de algún tipo A
como si fuera algún tipo B
. La sintaxis típica es esta:
def f[A <% B](a: A) = a.bMethod
En otras palabras, A
debe tener una conversión implícita a B
disponible, para que uno pueda llamar a B
métodos en un objeto de tipo A
. El uso más común de los límites de vista en la biblioteca estándar (antes de Scala 2.8.0, de todos modos), es Ordered
así:
def f[A <% Ordered[A]](a: A, b: A) = if (a < b) a else b
Debido a que uno puede convertir A
en an Ordered[A]
, y porque Ordered[A]
define el método <(other: A): Boolean
, puedo usar la expresióna < b
.
Tenga en cuenta que los límites de vista están en desuso , debe evitarlos.
Los límites de contexto se introdujeron en Scala 2.8.0, y generalmente se usan con el llamado patrón de clase de tipo , un patrón de código que emula la funcionalidad proporcionada por las clases de tipo Haskell, aunque de una manera más detallada.
Si bien un límite de vista se puede usar con tipos simples (por ejemplo, A <% String
), un límite de contexto requiere un tipo parametrizado , como el Ordered[A]
anterior, pero diferente String
.
Un enlace de contexto describe un valor implícito , en lugar de ver la conversión implícita del enlace . Se utiliza para declarar que para algún tipo A
, hay un valor implícito de tipo B[A]
disponible. La sintaxis es así:
def f[A : B](a: A) = g(a) // where g requires an implicit value of type B[A]
Esto es más confuso que el límite de la vista porque no está claro de inmediato cómo usarlo. El ejemplo común de uso en Scala es este:
def f[A : ClassManifest](n: Int) = new Array[A](n)
Una Array
inicialización en un tipo parametrizado requiere ClassManifest
que esté disponible, por razones arcanas relacionadas con la eliminación de tipo y la naturaleza de no eliminación de las matrices.
Otro ejemplo muy común en la biblioteca es un poco más complejo:
def f[A : Ordering](a: A, b: A) = implicitly[Ordering[A]].compare(a, b)
Aquí, implicitly
se utiliza para recuperar el valor implícito que queremos, uno de tipo Ordering[A]
, qué clase define el método compare(a: A, b: A): Int
.
Veremos otra forma de hacer esto a continuación.
No debería sorprender que tanto los límites de vista como los límites de contexto se implementen con parámetros implícitos, dada su definición. En realidad, la sintaxis que mostré son azúcares sintácticos para lo que realmente sucede. Vea a continuación cómo se quita el azúcar:
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)
Entonces, naturalmente, uno puede escribirlos en su sintaxis completa, que es especialmente útil para los límites de contexto:
def f[A](a: A, b: A)(implicit ord: Ordering[A]) = ord.compare(a, b)
Los límites de vista se usan principalmente para aprovechar el patrón de pimp my library , a través del cual se "agregan" métodos a una clase existente, en situaciones en las que se desea devolver el tipo original de alguna manera. Si no necesita devolver ese tipo de ninguna manera, entonces no necesita un límite de vista.
El ejemplo clásico de uso de vista enlazada es el manejo Ordered
. Tenga en cuenta que Int
no es Ordered
, por ejemplo, aunque hay una conversión implícita. El ejemplo dado anteriormente necesita un límite de vista porque devuelve el tipo no convertido:
def f[A <% Ordered[A]](a: A, b: A): A = if (a < b) a else b
Este ejemplo no funcionará sin límites de vista. Sin embargo, si tuviera que devolver otro tipo, ya no necesitaría una vista enlazada:
def f[A](a: Ordered[A], b: A): Boolean = a < b
La conversión aquí (si es necesario) ocurre antes de pasarle el parámetro f
, por lo f
que no necesita saberlo.
Además Ordered
, el uso más común de la biblioteca es el manejo String
y Array
, que son clases de Java, como si fueran colecciones de Scala. Por ejemplo:
def f[CC <% Traversable[_]](a: CC, b: CC): CC = if (a.size < b.size) a else b
Si se intenta hacer esto sin límites de vista, el tipo de retorno de a String
sería a WrappedString
(Scala 2.8), y de manera similar para Array
.
Lo mismo sucede incluso si el tipo solo se usa como parámetro de tipo del tipo de retorno:
def f[A <% Ordered[A]](xs: A*): Seq[A] = xs.toSeq.sorted
Los límites de contexto se utilizan principalmente en lo que se conoce como patrón de clase de tipo, como referencia a las clases de tipo de Haskell. Básicamente, este patrón implementa una alternativa a la herencia haciendo que la funcionalidad esté disponible a través de una especie de patrón adaptador implícito.
El ejemplo clásico es Scala 2.8 Ordering
, que reemplazó a Ordered
toda la biblioteca de Scala. El uso es:
def f[A : Ordering](a: A, b: A) = if (implicitly[Ordering[A]].lt(a, b)) a else b
Aunque generalmente verás eso escrito así:
def f[A](a: A, b: A)(implicit ord: Ordering[A]) = {
import ord.mkOrderingOps
if (a < b) a else b
}
Que aprovechan algunas conversiones implícitas Ordering
que permiten el estilo tradicional del operador. Otro ejemplo en Scala 2.8 es el Numeric
:
def f[A : Numeric](a: A, b: A) = implicitly[Numeric[A]].plus(a, b)
Un ejemplo más complejo es el nuevo uso de la colección CanBuildFrom
, pero ya hay una respuesta muy larga al respecto, por lo que lo evitaré aquí. Y, como se mencionó anteriormente, está el ClassManifest
uso, que se requiere para inicializar nuevas matrices sin tipos concretos.
El contexto vinculado con el patrón de tipo de clase es mucho más probable que lo usen sus propias clases, ya que permiten la separación de preocupaciones, mientras que los límites de vista se pueden evitar en su propio código mediante un buen diseño (se usa principalmente para evitar el diseño de otra persona) )
Aunque ha sido posible durante mucho tiempo, el uso de límites de contexto realmente ha despegado en 2010, y ahora se encuentra hasta cierto punto en la mayoría de las bibliotecas y marcos más importantes de Scala. Sin embargo, el ejemplo más extremo de su uso es la biblioteca Scalaz, que aporta gran parte del poder de Haskell a Scala. Recomiendo leer sobre patrones de clases de tipos para familiarizarse más con todas las formas en que se puede usar.
EDITAR
Preguntas relacionadas de interés: