scala generics shapeless

scala - Shapeless: Generic.Aux



generics (1)

Las cuestiones relacionadas con la forma en que se implementan Generic y TypeClass y lo que hacen son tan diferentes que probablemente merecen preguntas separadas, por lo que me quedo con Generic aquí.

Generic proporciona una asignación de clases de casos (y tipos potencialmente similares) a listas heterogéneas. Cualquier clase de caso tiene una representación hlist única, pero cualquier hlist dada corresponde a un número muy, muy grande de clases de casos potenciales. Por ejemplo, si tenemos las siguientes clases de casos:

case class Foo(i: Int, s: String) case class Bar(x: Int, y: String)

La representación hlist proporcionada por Generic para Foo y Bar es Int :: String :: HNil , que también es la representación para (Int, String) y cualquier otra clase de caso que podamos definir con estos dos tipos en este orden.

(Como nota al LabelledGeneric , LabelledGeneric nos permite distinguir entre Foo y Bar , ya que incluye los nombres de los miembros en la representación como cadenas de nivel de tipo).

Por lo general, queremos poder especificar la clase de caso y dejar que Shapeless resuelva la representación genérica (única), y hacer que Repr un miembro de tipo (en lugar de un parámetro de tipo) nos permite hacerlo de manera bastante limpia. Si el tipo de representación hlist fuera un parámetro de tipo, entonces sus métodos de find tendrían que tener un parámetro de tipo Repr , lo que significa que no podría especificar solo la T y tener la Repr inferida.

Convertir a Repr un miembro de tipo solo tiene sentido porque el Repr está determinado de forma única por el primer parámetro de tipo. Imagina una clase de tipos como Iso[A, B] que es testigo de que A y B son isomorfos. Esta clase de tipo es muy parecida a la Generic , pero A no es la única dermina B , no podemos simplemente preguntar "¿cuál es el tipo que es isomorfo para A ?", Por lo que no sería útil hacer de B un miembro de tipo ( aunque podríamos si realmente quisiéramos: Iso[A] simplemente no significaría nada).

El problema con los miembros de tipo es que son fáciles de olvidar, y una vez que se han ido, se han ido para siempre. El hecho de que el tipo de retorno de su find1 no sea refinado (es decir, no incluye el miembro de tipo) significa que la instancia Generic que devuelve es bastante inútil. Por ejemplo, el tipo estático de res0 aquí también podría ser Any :

scala> import shapeless._ import shapeless._ scala> def find1[T](implicit gen: Generic[T]): Generic[T] = gen find1: [T](implicit gen: shapeless.Generic[T])shapeless.Generic[T] scala> case class Foo(i: Int, s: String) defined class Foo scala> find1[Foo].to(Foo(1, "ABC")) res0: shapeless.Generic[Foo]#Repr = 1 :: ABC :: HNil scala> res0.head <console>:15: error: value head is not a member of shapeless.Generic[Foo]#Repr res0.head ^

Cuando la macro Generic.materialize de Generic.materialize crea la instancia Generic[Foo] que estamos solicitando, se escribe estáticamente como un Generic[Foo] { type Repr = Int :: String :: HNil } , por lo que el argumento gen que el compilador entrega find1 tiene toda la información estática que necesitamos. El problema es que luego, de forma explícita, enviamos ese tipo a un Generic[Foo] sin refinar, y desde ese momento en el compilador no sabemos qué es el Repr para esa instancia.

Los tipos dependientes de la ruta de Scala nos dan una manera de no olvidar el refinamiento sin agregar otro parámetro de tipo a nuestro método. En su find2 , el compilador conoce estáticamente el Repr para el gen entrante, por lo que cuando dice que el tipo de retorno es Generic[T] { type Repr = gen.Repr } , podrá realizar un seguimiento de esa información:

scala> find2[Foo].to(Foo(1, "ABC")) res2: shapeless.::[Int,shapeless.::[String,shapeless.HNil]] = 1 :: ABC :: HNil scala> res2.head res3: Int = 1

Para resumir: Generic tiene un parámetro de tipo T que determina de forma única su miembro de tipo Repr , Repr es un miembro de tipo en lugar de un parámetro de tipo para que no tengamos que incluirlo en todas nuestras firmas de tipo y tipos dependientes de la ruta haga esto posible, permitiéndonos realizar un seguimiento de Repr aunque no esté en nuestras firmas de tipo.

Estoy tratando de entender cómo funciona Generic (y TypeClass también). El wiki de github es muy escaso en ejemplos y documentación. ¿Hay una página de publicación / documentación canónica que describa Generic y TypeClass en detalle?

En concreto, ¿cuál es la diferencia entre estos dos métodos ?:

def find1[T](implicit gen: Generic[T]): Generic[T] = gen def find2[T](implicit gen: Generic[T]): Generic[T] { type Repr = gen.Repr } = gen

dado

object Generic { type Aux[T, Repr0] = Generic[T] { type Repr = Repr0 } def apply[T](implicit gen: Generic[T]): Aux[T, gen.Repr] = gen implicit def materialize[T, R]: Aux[T, R] = macro GenericMacros.materialize[T, R] }