¿Cuáles son los límites de contexto y vista de Scala?

267

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!

chrsan
fuente

Respuestas:

477

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:

¿Qué es una vista enlazada?

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, Adebe tener una conversión implícita a Bdisponible, para que uno pueda llamar a Bmé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 Orderedasí:

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

Debido a que uno puede convertir Aen 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.

¿Qué es un contexto limitado?

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 Arrayinicialización en un tipo parametrizado requiere ClassManifestque 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í, implicitlyse 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.

¿Cómo se implementan los límites de vista y los límites de contexto?

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)

¿Para qué se utilizan los límites de vista?

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 Intno 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 fque no necesita saberlo.

Además Ordered, el uso más común de la biblioteca es el manejo Stringy 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 Stringserí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

¿Para qué se usan los límites de contexto?

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 Orderedtoda 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 Orderingque 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 ClassManifestuso, 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:

Daniel C. Sobral
fuente
9
Muchas gracias. Sé que esto ha sido respondido antes, y tal vez no leí con suficiente atención entonces, pero su explicación aquí es la más clara que he visto. Entonces, gracias de nuevo.
chrsan
3
@chrsan Agregué dos secciones más, entrando en más detalles sobre dónde se usa cada una.
Daniel C. Sobral
2
Creo que esta es una excelente explicación. Me gustaría traducir esto para mi blog alemán (dgronau.wordpress.com) si te parece bien.
Landei
3
Esta es, con mucho, la mejor y más completa explicación de este tema que he encontrado hasta ahora. ¡Muchas gracias de hecho!
fotNelton
2
Entonces, cuando saldrá tu libro Scala, y dónde puedo comprarlo :)
wfbarksdale