varias superponer studio lineas histogramas graficos graficas scala types shapeless type-level-computation

scala - studio - superponer graficas en r



¿Por qué se requiere la técnica Aux para los cálculos de nivel de tipo? (1)

Hay dos preguntas separadas aquí:

  1. ¿Por qué Shapeless usa miembros de tipo en lugar de parámetros de tipo en algunos casos en algunas clases de tipo?
  2. ¿Por qué Shapeless incluye alias de tipo Aux en los objetos complementarios de estas clases de tipo?

Comenzaré con la segunda pregunta porque la respuesta es más directa: los alias de tipo Aux son una conveniencia sintáctica. Nunca tienes que usarlos. Por ejemplo, supongamos que queremos escribir un método que solo se compile cuando se invoque con dos listas que tienen la misma longitud:

import shapeless._, ops.hlist.Length def sameLength[A <: HList, B <: HList, N <: Nat](a: A, b: B)(implicit al: Length.Aux[A, N], bl: Length.Aux[B, N] ) = ()

La clase de tipo Length tiene un parámetro de tipo (para el tipo HList ) y un miembro de tipo (para el Nat ). La sintaxis de Length.Aux hace que sea relativamente fácil referirse al miembro de tipo Nat en la lista de parámetros implícitos, pero es solo una conveniencia; lo siguiente es exactamente equivalente:

def sameLength[A <: HList, B <: HList, N <: Nat](a: A, b: B)(implicit al: Length[A] { type Out = N }, bl: Length[B] { type Out = N } ) = ()

La versión Aux tiene un par de ventajas sobre escribir las mejoras de tipo de esta manera: es menos ruidosa y no nos obliga a recordar el nombre del miembro de tipo. Sin embargo, estos son problemas puramente ergonómicos: los alias Aux hacen que nuestro código sea un poco más fácil de leer y escribir, pero no cambian lo que podemos o no podemos hacer con el código de manera significativa.

La respuesta a la primera pregunta es un poco más compleja. En muchos casos, incluido my sameLength , no es ventajoso que Out sea ​​un miembro de tipo en lugar de un parámetro de tipo. Debido a que Scala no permite múltiples secciones de parámetros implícitos , necesitamos que N sea ​​un parámetro de tipo para nuestro método si queremos verificar que las dos instancias de Length tienen el mismo tipo de Out . En ese momento, Out on Length podría ser también un parámetro de tipo (al menos desde nuestra perspectiva como los autores de sameLength ).

En otros casos, sin embargo, podemos aprovechar el hecho de que Shapeless a veces (hablaré específicamente de dónde en un momento) usa miembros de tipo en lugar de parámetros de tipo. Por ejemplo, supongamos que queremos escribir un método que devolverá una función que convertirá un tipo de clase de caso específico en una lista HList :

def converter[A](implicit gen: Generic[A]): A => gen.Repr = a => gen.to(a)

Ahora podemos usarlo así:

case class Foo(i: Int, s: String) val fooToHList = converter[Foo]

Y obtendremos un buen Foo => Int :: String :: HNil . Si el Repr Generic fuera un parámetro de tipo en lugar de un miembro de tipo, tendríamos que escribir algo como esto en su lugar:

// Doesn''t compile def converter[A, R](implicit gen: Generic[A, R]): A => R = a => gen.to(a)

Scala no admite la aplicación parcial de parámetros de tipo, por lo que cada vez que llamamos a este método (hipotético) deberíamos especificar ambos parámetros de tipo ya que queremos especificar A :

val fooToHList = converter[Foo, Int :: String :: HNil]

Esto básicamente hace que no valga la pena, ya que el objetivo principal era dejar que la maquinaria genérica resuelva la representación.

En general, cuando un tipo está determinado únicamente por los otros parámetros de una clase de tipo, Shapeless lo convertirá en un miembro de tipo en lugar de un parámetro de tipo. Cada clase de caso tiene una sola representación genérica, por lo que Generic tiene un parámetro de tipo (para el tipo de clase de caso) y un miembro de tipo (para el tipo de representación); cada HList tiene una longitud única, por lo que Length tiene un parámetro de tipo y un miembro de tipo, etc.

El hecho de que los tipos de miembros sean determinados de forma única en lugar de los parámetros de tipo significa que si queremos usarlos solo como tipos dependientes de la ruta (como en el primer converter anterior), podemos hacerlo, pero si queremos usarlos como si fueran parámetros de tipo , siempre podemos escribir el refinamiento de tipo (o la versión Aux sintácticamente mejor). Si Shapeless hizo estos tipos de parámetros de tipo desde el principio, no sería posible ir en la dirección opuesta.

Como nota al margen, esta relación entre los "parámetros" de tipo de una clase de tipo (estoy usando comillas ya que pueden no ser parámetros en el sentido literal de Scala) se denomina "dependencia funcional" en idiomas como Haskell, pero no debería Siento que necesitas entender algo acerca de las dependencias funcionales en Haskell para entender lo que sucede en Shapeless.

Estoy bastante seguro de que me estoy perdiendo algo aquí, ya que soy bastante nuevo en Shapeless y estoy aprendiendo, pero ¿cuándo se requiere realmente la técnica Aux? Veo que se usa para exponer una declaración de type elevándola a la firma de otra definición de type "complementaria".

trait F[A] { type R; def value: R } object F { type Aux[A,RR] = F[A] { type R = RR } }

¿Pero no es esto casi equivalente a simplemente poner R en la firma de tipo F?

trait F[A,R] { def value: R } implicit def fint = new F[Int,Long] { val value = 1L } implicit def ffloat = new F[Float,Double] { val value = 2.0D } def f[T,R](t:T)(implicit f: F[T,R]): R = f.value f(100) // res4: Long = 1L f(100.0f) // res5: Double = 2.0

Veo que el tipo dependiente de la ruta traería beneficios si uno pudiera usarlos en las listas de argumentos, pero sabemos que no podemos hacerlo

def g[T](t:T)(implicit f: F[T], r: Blah[f.R]) ...

por lo tanto, todavía estamos obligados a poner un parámetro de tipo adicional en la firma de g . Al utilizar la técnica Aux , también se nos exige dedicar más tiempo a escribir el object complementario. Desde el punto de vista del uso, a un usuario ingenuo como yo le parecería que no hay ningún beneficio en el uso de tipos dependientes de la ruta.

Solo se me ocurre un caso, es decir, para un cálculo de nivel de tipo dado se devuelve más de un resultado de nivel de tipo, y es posible que desee utilizar solo uno de ellos.

Supongo que todo se reduce a mí con respecto a algo en mi ejemplo simple.