2017-01-13 2 views
2

Quelle est la bonne façon d'implémenter des appels récursifs dans une méthode typeclass sans forme?Shapeless: appels récursifs dans la classe de type

(Une première mise en garde:.! J'apprends informes, donc il peut y avoir des réponses évidentes/alternatives Je ne sais pas encore Toute aide est grandement appréciée)

J'ai une classe de types qui convertit une classe de cas en une structure imbriquée d'autres objets - similaire à et inspirée par l'exemple ToMapRec mentionné dans this stackoverflow question - sauf qu'au lieu de renvoyer une Map potentiellement récursive, elle renvoie une classe de cas composée de membres potentiellement récursifs. Ainsi, au lieu de convertir une instance de MyType:

trait GetsConverted 
case class MyType(someData: String, one: GetsConverted, other: GetsConverted) extends GetsConverted 
case class MyOtherType(blah: AndSoOn) extends GetsConverted 
case class AndSoOn(eventualFinalValue: Int) 

dans un éventuellement récursive/imbriquée Map[String,Any] (comme dans l'autre question), il retourne quelque chose comme une instance de:

case class ReturnType(name: String, data: Option[Any], more: Set[ReturnType]) 

Pour créer le more Le membre semble avoir besoin de faire un appel récursif à l'intérieur de la classe de type. Mais l'appel de la méthode de conversion de la classe type à l'intérieur d'une autre méthode nécessite l'insertion dans les paramètres implicites de tous les types de cette fonction de l'appel le plus externe. Ainsi, au lieu d'une conversion de classe de types comme:

implicit def hconsToMapRec0[K, V, A <: HList, B <: HList](implicit 
    wit: Witness.Aux[K], 
    gen: LabelledGeneric.Aux[V, R], 
    tmrH: Lazy[ToMapRec[A]], 
    tmrT: Lazy[ToMapRec[B]] 
): ReturnType = ??? 

une méthode profondeur de trois (je suppose), il faudrait une signature de fonction quelque chose comme:

implicit def hconsToMapRec0[K, V, A <: HList, B <: HList, W, C <: HList, D <: HList, X, E <: HList, F <: HList](implicit 
    wit: Witness.Aux[K], 
    gen0: LabelledGeneric.Aux[V, A], 
    tmrH0: Lazy[ToMapRec[A]], 
    tmrT0: Lazy[ToMapRec[B]], 
    gen1: LabelledGeneric.Aux[W, C], 
    tmrH1: Lazy[ToMapRec[C]], 
    tmrT1: Lazy[ToMapRec[D]], 
    gen2: LabelledGeneric.Aux[X, E], 
    tmrH2: Lazy[ToMapRec[E]], 
    tmrT2: Lazy[ToMapRec[F]] 
): ReturnType = ??? 

Ou peut-être pire. En général, cette approche nécessiterait une méthode dont les paramètres implicites sont multipliés par autant de niveaux de profondeur dans cette récursivité. Et le nombre de niveaux de profondeur n'est connu qu'à l'exécution. Donc, cela ne peut pas être le moyen de le faire. Cela ressemble aux méthodes 22-arity codées en dur dans la bibliothèque de collections scala. Puisque la raison d'être de Shapeless est de faire abstraction de l'arité, cela semble être un problème appelant à plus de Shapeless-foo que j'ai appris jusqu'à présent.

La question est donc: comment voulez-vous écrire un classe de types informes pour convertir une chose classe de cas arbitraire structuré comme l'exemple MyType ci-dessus dans une valeur définie récursivement comme:

ReturnType("MyType", Some("someData"), Set(
    ReturnType("MyOtherType", None, Set(
    ReturnType("AndSoOn", Some(10), Set()) 
)), 
    ReturnType("MyOtherType", None, Set(
    ReturnType("AndSoOn", Some(20), Set()) 
)) 
)) 

Répondre

1

J'ai réussi à obtenir quelque chose près de votre exemple avec l'implémentation ci-dessous. La plupart est similaire à la réponse à la question this. La différence est qu'il se convertit à ReturnType et j'ai également ajouté quelques cas pour le cas Coproduct (qui est la représentation générique d'un sealed trait).

Donc, avec le code suivant:

val gen = LabelledGeneric[GetsConverted] 
val m = MyType("someData", MyOtherType(AndSoOn(10)), MyOtherType(AndSoOn(20))) 
val tmr = ToReturnTypeRec[gen.Repr] 
val returnType = tmpr(gen.to(m)) 

Vous obtenez le résultat

ReturnType(MyType,Some(someData),Set(
    ReturnType(MyOtherType,None,Set(
     ReturnType(,Some(20),Set()) 
    )), 
    ReturnType(MyOtherType,None,Set(
     ReturnType(,Some(10),Set()) 
    )) 
)) 

Voici la mise en œuvre:

trait ToReturnTypeRec[L] { def apply(l: L): ReturnType } 

trait LowPriorityToReturnTypeRec { 
implicit def hconsToReturnTypeRec1[K <: Symbol, V, T <: HList](implicit 
    wit: Witness.Aux[K], 
    tmrT: ToReturnTypeRec[T] 
): ToReturnTypeRec[FieldType[K, V] :: T] = new ToReturnTypeRec[FieldType[K, V] :: T] { 
    def apply(l: FieldType[K, V] :: T): ReturnType = 
     tmrT(l.tail) match { 
     case ReturnType(n,d,m) => ReturnType("", Some(l.head), m) 
     } 
    } 
} 

object ToReturnTypeRec extends LowPriorityToReturnTypeRec { 
    def apply[T](implicit tmr: ToReturnTypeRec[T]) = tmr 
    implicit val hnilToReturnTypeRec: ToReturnTypeRec[HNil] = new ToReturnTypeRec[HNil] { 
    def apply(l: HNil): ReturnType = ReturnType("", None, Set()) 
    } 

    implicit def hconsToReturnTypeRec0[K <: Symbol, V, T <: HList, R](implicit 
    // wit: Witness.Aux[K], 
    gen: LabelledGeneric.Aux[V, R], 
    tmrH: Lazy[ToReturnTypeRec[R]], 
    tmrT: ToReturnTypeRec[T] 
): ToReturnTypeRec[FieldType[K, V] :: T] = new ToReturnTypeRec[FieldType[K, V] :: T] { 
    def apply(l: FieldType[K, V] :: T): ReturnType = 
     tmrT(l.tail) match { 
     case ReturnType(n,d,m) => ReturnType(n, d, m + tmrH.value(gen.to(l.head))) 
     } 
    } 

    implicit val cnillToReturnTypeRec: ToReturnTypeRec[CNil] = new ToReturnTypeRec[CNil] { 
    def apply(c: CNil): ReturnType = ReturnType("Impossible", None, Set()) 
    } 

    implicit def cconsToReturnTypeRec0[K <: Symbol, V <: Product, T <: Coproduct](implicit 
    wit: Witness.Aux[K], 
    tmrH: Lazy[ToReturnTypeRec[V]], 
    tmrT: Lazy[ToReturnTypeRec[T]] 
): ToReturnTypeRec[FieldType[K,V] :+: T] = new ToReturnTypeRec[FieldType[K, V] :+: T] { 
    def apply(c: FieldType[K,V] :+: T): ReturnType = { 
     c match { 
     case Inl(h) => tmrH.value(h) match { 
      case ReturnType(_,d,m) => ReturnType(wit.value.name, d, m) 
     } 
     case Inr(t) => tmrT.value(t) 
     } 
    } 
    } 

    implicit def genericToReturnTypeRec[P, R](implicit 
    gen: LabelledGeneric.Aux[P, R], 
    tmr: Lazy[ToReturnTypeRec[R]] 
): ToReturnTypeRec[P] = new ToReturnTypeRec[P] { 
    def apply(p: P): ReturnType = tmr.value(gen.to(p)) 
    } 
} 
+0

Merci pour cela! +1 pour l'effort, mais je n'ai probablement pas fait un bon travail de mise en évidence où la récursion est nécessaire dans la requête originale.C'est assez bien défini dans le même problème que nous avons tous les deux liés. Mais le problème survient lorsque le site d'appel se trouve dans une autre fonction (récursive). Faire ceci une fois est simple, mais comment appelleriez-vous récursivement: 'val returnType = tmpr (gen.to (m))'? – Ryan