Merge HList with unknown types

I have a hierarchy of a complex type, but there are two main features for breaking it up: Convertable and Conversion[A <: Convertable, B <: Convertable , for example. There is a conversion that can convert a Miley machine into a Moore machine. Each Conversion[A,B] has a convert(automaton: A) : B method convert(automaton: A) : B

Now I want to introduce the concept of smart conversions, which are basically a list of normal conversions that will be performed one after another. Therefore, I introduced the AutoConversion by extending Conversion, which has the val path : HList , to represent the conversion chain and must implement the convert method, so AutoConversions just have to provide a list of actual conversions to take. I think you could implement this with fold over path , so here is my first attempt:

 package de.uni_luebeck.isp.conversions import shapeless._ import shapeless.ops.hlist.LeftFolder trait AutoConversion[A <: Convertable, B <: Convertable] extends Conversion[A, B] { val path: HList object combiner extends Poly { implicit def doSmth[C <: Convertable, D <: Convertable] = use((conv : Conversion[C, D] , automaton : C) => conv.convert(automaton)) 

}

  override def convert(startAutomaton: A): B = { path.foldLeft(startAutomaton)(combiner) } } 

This will not work because an implicit folder cannot be found, so I assume I need to provide more type information for the compiler somewhere, but I don’t know where

+6
source share
1 answer

You are right to need more information about the type, and in general, if you have a value with HList as a static type, you probably have to change your approach. Essentially, you can't do anything with an HList if all you know is that it is an HList (other than the values ​​that precede it), and you usually only write HList as a type constraint.

In your case, what you are describing is a kind of sequence aligned in type. Before moving on with this approach, I suggest that you be sure what you really need. One of the nice things about functions (and types of functions like your Conversion ) is that they make up: you have A => B and B => C , and you create them in A => C and you can forget about B forever. You get a nice clean black box, which is usually exactly what you want.

In some cases, it may be helpful to be able to compose functionally similar things so that you can reflect on parts of the pipeline. I am going to assume that this is one of those cases, but you must confirm it yourself. If this is not so, you are lucky because what comes is random.

I will use these types:

 trait Convertable trait Conversion[A <: Convertable, B <: Convertable] { def convert(a: A): B } 

We can define a type class that indicates that a particular HList consists of one or more transformations whose types line up:

 import shapeless._ trait TypeAligned[L <: HList] extends DepFn1[L] { type I <: Convertable type O <: Convertable type Out = Conversion[I, O] } 

L contains all the type information about the pipeline, and I and O are the types of its endpoints.

Next, we need instances for this type (note that this must be defined together with the above attribute in order for these two to be related):

 object TypeAligned { type Aux[L <: HList, A <: Convertable, B <: Convertable] = TypeAligned[L] { type I = A type O = B } implicit def firstTypeAligned[ A <: Convertable, B <: Convertable ]: TypeAligned.Aux[Conversion[A, B] :: HNil, A, B] = new TypeAligned[Conversion[A, B] :: HNil] { type I = A type O = B def apply(l: Conversion[A, B] :: HNil): Conversion[A, B] = l.head } implicit def composedTypeAligned[ A <: Convertable, B <: Convertable, C <: Convertable, T <: HList ](implicit tta: TypeAligned.Aux[T, B, C] ): TypeAligned.Aux[Conversion[A, B] :: T, A, C] = new TypeAligned[Conversion[A, B] :: T] { type I = A type O = C def apply(l: Conversion[A, B] :: T): Conversion[A, C] = new Conversion[A, C] { def convert(a: A): C = tta(l.tail).convert(l.head.convert(a)) } } } 

And now you can write a version of your AutoConversion that tracks all the type information about the pipeline:

 class AutoConversion[L <: HList, A <: Convertable, B <: Convertable]( path: L )(implicit ta: TypeAligned.Aux[L, A, B]) extends Conversion[A, B] { def convert(a: A): B = ta(path).convert(a) } 

And you can use it as follows:

 case class AutoA(i: Int) extends Convertable case class AutoB(s: String) extends Convertable case class AutoC(c: Char) extends Convertable val ab: Conversion[AutoA, AutoB] = new Conversion[AutoA, AutoB] { def convert(a: AutoA): AutoB = AutoB(aitoString) } val bc: Conversion[AutoB, AutoC] = new Conversion[AutoB, AutoC] { def convert(b: AutoB): AutoC = AutoC(bslift(3).getOrElse('-')) } val conv = new AutoConversion(ab :: bc :: HNil) 

And conv will have the expected static type (and implement Conversion[AutoA, AutoC] ).

+9
source

All Articles