St. - 1 year ago 91
Scala Question

# Map single type HList to HList of target types

I have trait-marker

``````trait TypedTrait {
type TYPE
}
``````

and the realization

``````case class TypedString[U](value: String) extends TypedTrait {
type TYPE = U
}
``````

And I want to map
`HList`
of
`String`
into
`HList`
of
`TypedString`
in accordance with
`TypedString`
's type parameters.

The simplest way is to create
`convert`
method (as described in Shapeless map HList depending on target types):

``````    val list = "Hello" :: "world" :: HNil

val mapped: TypedString[Int] :: TypedString[Boolean] :: HNil =
convert[TypedString[Int] :: TypedString[Boolean] :: HNil](list)
``````

But I'd like to avoid redundant parameterization and use something like this:

``````    val mapped: TypedString[Int] :: TypedString[Boolean] :: HNil =
convert[Int :: Boolean :: HNil](list)
``````

Complete code example for the first solution:

``````      import shapeless._

trait TypedTrait {
type TYPE
}

case class TypedString[U](value: String) extends TypedTrait {
type TYPE = U
}

trait Convert[I <: HList, O <: HList] { def apply(i: I): O }

object Convert extends LowPriorityConvertInstances {
implicit val convertHNil: Convert[HNil, HNil] = new Convert[HNil, HNil] {
def apply(i: HNil): HNil = i
}

implicit def convertHConsTS[TS, T <: HList, TO <: HList](implicit
c: Convert[T, TO]
): Convert[String :: T, TypedString[TS] :: TO] =
new Convert[String :: T, TypedString[TS] :: TO] {
def apply(i: String :: T): TypedString[TS] :: TO = TypedString[TS](i.head) :: c(i.tail)
}
}

sealed class LowPriorityConvertInstances {
implicit def convertHCons[H, T <: HList, TO <: HList](implicit
c: Convert[T, TO]
): Convert[H :: T, H :: TO] = new Convert[H :: T, H :: TO] {
def apply(i: H :: T): H :: TO = i.head :: c(i.tail)
}
}

class PartiallyAppliedConvert[O <: HList] {
def apply[I <: HList](i: I)(implicit c: Convert[I, O]): O = c(i)
}

def convert[O <: HList]: PartiallyAppliedConvert[O] =
new PartiallyAppliedConvert[O]

val list = "Hello" :: "world" :: HNil

val mapped: TypedString[Int] :: TypedString[String] :: HNil =
convert[TypedString[Int] :: TypedString[String] :: HNil](list)
``````

You can achive this by having three `HList` type arguments in `Convert`:

• the type of the actual `HList` passed to `convert` (e.g., `String :: String :: HNil`)
• the type parameter prescribed by the user (e.g., `Int :: Boolean :: HNil`)
• the output type – basically the prescribed `HList` wrapped in `TypedString`: e.g., `TypedString[Int] :: TypedString[Boolean] :: HNil`.

The output type can be completely calculated from the prescribed `HList`, so I'd use the `Aux` pattern commonly employed with `shapeless` code:

``````trait Convert[In <: HList, Prescribed <: HList] {
type Out <: HList
def apply(i: In): Out
}

object Convert {
type Aux[I <: HList, P <: HList, O <: HList] = Convert[I, P] { type Out = O }

// Adapt the implicits accordingly.
// The low priority one is left as an exercise to the reader.

implicit val convertHNil: Convert.Aux[HNil, HNil, HNil] =
new Convert[HNil, HNil] {
type Out = HNil
def apply(i: HNil): HNil = i
}

implicit def convertHConsTS[TS, TI <: HList, TP <: HList, TO <: HList](implicit
c: Convert.Aux[TI, TP, TO]
): Convert.Aux[String :: TI, TS :: TP, TypedString[TS] :: TO] =
new Convert[String :: TI, TS :: TP] {
type Out = TypedString[TS] :: TO
def apply(i: String :: TI): TypedString[TS] :: TO =
}
}

class PartiallyAppliedConvert[P <: HList] {
def apply[I <: HList](i: I)(implicit c: Convert[I, P]): c.Out = c(i)
}

def convert[O <: HList]: PartiallyAppliedConvert[O] =
new PartiallyAppliedConvert[O]

val list = "Hello" :: "world" :: HNil

val mapped = convert[Int :: String :: HNil](list)
``````

Result:

``````scala> mapped
res3: shapeless.::[com.Main.TypedString[Int],shapeless.::[com.Main.TypedString[String],shapeless.HNil]] = TypedString(Hello) :: TypedString(world) :: HNil
``````

I believe it may be possible to achieve this using some operations provided with `shapeless` (`shapeless.ops.hlist.Mapped`, `shapeless.ops.hlist.HKernel`, or `shapeless.ops.hlist.RightFolder` look appropriate), but I don't know how to write a `Poly` function, that takes a type argument and a normal argument. Any tips would be welcome.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download