def - scala implicit conversion types



¿Qué son los límites de vista y contexto de Scala? (1)

Pensé que esto ya se había preguntado, pero, de ser así, la pregunta no es evidente en la barra "relacionada". Asi que aqui esta:

¿Qué es una vista enlazada?

Una vista enlazada fue 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 debería tener una conversión implícita a B disponible, de modo que uno pueda llamar a B métodos B 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 con 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 un Ordered[A] , y porque Ordered[A] define el método <(other: A): Boolean , puedo usar la expresión a < b .

Tenga en cuenta que los límites de vista están en desuso , debe evitarlos.

¿Qué es un límite de contexto?

Los límites de contexto se introdujeron en Scala 2.8.0, y normalmente 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.

Mientras que 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 a diferencia de String .

Un límite de contexto describe un valor implícito, en lugar de la conversión implícita del límite de vista. Se utiliza para declarar que para algún tipo A , hay un valor implícito de tipo B[A] disponible. La sintaxis es la siguiente:

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á inmediatamente claro cómo usarlo. El ejemplo común de uso en Scala es este:

def f[A : ClassManifest](n: Int) = new Array[A](n)

Una inicialización de Array en un tipo parametrizado requiere que esté disponible un ClassManifest , por razones arcanas relacionadas con el borrado de tipo y la naturaleza de no borrado de los arrays.

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 usa para recuperar el valor implícito que deseamos, uno de tipo Ordering[A] , cuya clase define el método de 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 deshacen de 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)

Así que, naturalmente, uno puede escribirlos en su sintaxis completa, lo cual es especialmente útil para los límites del contexto:

def f[A](a: A, b: A)(implicit ord: Ordering[A]) = ord.compare(a, b)

¿Para qué se usan los límites de visualización?

Los límites de vista se utilizan principalmente para aprovechar el patrón de pimp my library , mediante el cual uno "agrega" métodos a una clase existente, en situaciones en las que 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 del límite de vista es el manejo Ordered . Tenga en cuenta que Int no está 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, entonces ya no necesito un límite de vista:

def f[A](a: Ordered[A], b: A): Boolean = a < b

La conversión aquí (si es necesaria) ocurre antes de pasar el parámetro a f , por lo que f no necesita saberlo.

Además de Ordered , el uso más común de la biblioteca es el manejo de 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 uno intentara hacer esto sin límites de vista, el tipo de retorno de una String sería un WrappedString (Scala 2.8), y similar para Array .

Lo mismo sucede incluso si el tipo solo se usa como un 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 tipos , como referencia a las clases de tipos de Haskell. Básicamente, este patrón implementa una alternativa a la herencia al hacer que la funcionalidad esté disponible a través de una especie de patrón de adaptador implícito.

El ejemplo clásico es el Ordering Scala 2.8, 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 normalmente lo verás 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 dentro de Ordering que habilitan el estilo de operador tradicional. 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 de CanBuildFrom , pero ya hay una respuesta muy larga al respecto, así que lo CanBuildFrom aquí. Y, como se mencionó anteriormente, está el uso de ClassManifest , que se requiere para inicializar nuevas matrices sin tipos concretos.

El contexto vinculado con el patrón de clase de tipos es mucho más probable que lo utilicen sus propias clases, ya que permiten la separación de las preocupaciones, mientras que los límites de vista se pueden evitar en su propio código mediante un buen diseño (se utiliza principalmente para evitar el diseño de otra persona). ).

Aunque ha sido posible durante mucho tiempo, el uso de los límites del contexto realmente ha despegado en 2010, y ahora se encuentra en cierta medida en la mayoría de las bibliotecas y marcos más importantes de Scala. El ejemplo más extremo de su uso, sin embargo, es la biblioteca Scalaz, que aporta mucho poder de Haskell a Scala. Recomiendo leer sobre patrones de clase para familiarizarse más con todas las formas en que se puede utilizar.

EDITAR

Preguntas de interés relacionadas:

De manera simple, ¿cuáles son los límites de contexto y de vista y cuál es la diferencia entre ellos?

¡Algunos ejemplos fáciles de seguir también serían excelentes!





implicits