Reputation: 533
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)
Upvotes: 0
Views: 118
Reputation: 14224
You can achive this by having three HList
type arguments in Convert
:
HList
passed to convert
(e.g., String :: String :: HNil
)Int :: Boolean :: HNil
)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 =
TypedString[TS](i.head) :: c(i.tail)
}
}
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.
Upvotes: 1