Pourquoi la technique Aux est-elle requirejse pour les calculs au niveau du type?

Je suis sûr que je manque quelque chose ici, puisque je suis plutôt nouveau avec Shapeless et que j’apprends, mais quand est-ce que la technique Aux est réellement nécessaire ? Je vois qu’il est utilisé pour exposer une déclaration de type en la soulevant dans la signature d’une autre définition de type “compagnon”.

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

mais cela ne équivaut-il pas à mettre R dans la signature de type 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 

Je vois que le type dépendant du chemin apporterait des avantages si on pouvait les utiliser dans des listes d’arguments, mais nous soaps que nous ne pouvons pas le faire

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

ainsi, nous sums toujours obligés de mettre un paramètre de type supplémentaire dans la signature de g . En utilisant la technique Aux , nous devons également passer plus de temps à écrire l’ object compagnon. Du sharepoint vue de l’utilisation, un utilisateur naïf comme moi n’aurait aucun intérêt à utiliser des types dépendant du chemin.

Il n’ya qu’un seul cas auquel je puisse penser, c’est-à-dire que pour un calcul donné au niveau du type, plusieurs résultats au niveau du type sont renvoyés, et vous pouvez vouloir en utiliser un seul.

Je suppose que tout se résume à moi en oubliant quelque chose dans mon exemple simple.

Il y a deux questions distinctes ici:

  1. Pourquoi Shapeless utilise-t-il des membres de type plutôt que des parameters de type dans certains cas, dans certaines classes de type?
  2. Pourquoi Shapeless inclut-il des alias de type Aux dans les objects compagnons de ces classes de type?

Je commencerai par la deuxième question car la réponse est plus simple: les alias de type Aux sont entièrement pratiques sur le plan syntaxique. Vous n’avez jamais à les utiliser. Par exemple, supposons que nous voulions écrire une méthode qui ne comstackra que lorsqu’elle sera appelée avec deux listes de même longueur:

 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 classe de type Length a un paramètre de type (pour le type HList ) et un membre de type (pour le Nat ). La syntaxe Length.Aux rend relativement facile la référence au membre de type Nat dans la liste de parameters implicite, mais il ne s'agit que d'une commodité:

 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 version Aux a quelques avantages par rapport à l'écriture des raffinements de type de cette manière: elle est moins bruyante et ne nécessite pas de mémoriser le nom du membre de type. Ce sont des problèmes purement ergonomiques: les alias Aux rendent notre code un peu plus facile à lire et à écrire, mais ils ne changent pas de manière significative ce que nous pouvons ou ne pouvons pas faire avec le code.

La réponse à la première question est un peu plus complexe. Dans de nombreux cas, y compris mon sameLength , il n'y a aucun avantage à ce que Out soit un membre de type au lieu d'un paramètre de type. Comme Scala n'autorise pas plusieurs sections de parameters implicites , nous avons besoin que N soit un paramètre de type pour notre méthode si nous voulons vérifier que les deux instances Length ont le même type Out . À ce stade, le paramètre Out on Length pourrait tout aussi bien être un paramètre de type (du moins de notre sharepoint vue que les auteurs de sameLength ).

Dans d'autres cas, cependant, nous pouvons tirer parti du fait que Shapeless parfois (je parlerai spécifiquement dans un moment) utilise des membres de type plutôt que des parameters de type. Par exemple, supposons que nous voulions écrire une méthode qui retournera une fonction qui convertira un type de classe de cas spécifié en une HList :

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

Maintenant, nous pouvons l'utiliser comme ceci:

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

Et nous aurons un joli Foo => Int :: Ssortingng :: HNil . Si Repr était un paramètre de type à la place d'un membre de type, il faudrait plutôt écrire quelque chose comme ceci:

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

Scala ne supporte pas une application partielle des parameters de type, donc chaque fois que nous appelons cette méthode (hypothétique), il faudrait spécifier les deux parameters de type car nous voulons spécifier A :

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

Cela le rend fondamentalement inutile, car le but était de laisser les machines génériques déterminer la représentation.

En général, chaque fois qu'un type est uniquement déterminé par les autres parameters d'une classe de type, Shapeless en fait un membre de type au lieu d'un paramètre de type. Chaque classe de cas possède une seule représentation générique, de sorte que Generic a un paramètre de type (pour le type de classe de cas) et un membre de type (pour le type de représentation); chaque HList a une seule longueur, donc Length a un paramètre de type et un membre de type, etc.

Faire des types de types uniques détermine les membres à la place des parameters de type signifie que si nous voulons les utiliser uniquement comme types dépendant du chemin (comme dans le premier converter ci-dessus), nous pouvons, mais si nous voulons les utiliser comme des parameters de type , nous pouvons toujours soit écrire le raffinement de type (ou la version auxiliaire syntaxiquement plus agréable). Si Shapeless faisait que ces types saisissent les parameters depuis le début, il ne serait pas possible d'aller dans la direction opposée.

En guise de note, cette relation entre le type "parameters" d'une classe de type (j'utilise des guillemets, car ils ne peuvent pas être des parameters dans le sens littéral de Scala) est appelée "dépendance fonctionnelle" dans des langages comme Haskell, mais Vous avez besoin de comprendre quoi que ce soit sur les dépendances fonctionnelles dans Haskell pour savoir ce qui se passe dans Shapeless.