简体   繁体   中英

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 (eg, String :: String :: HNil )
  • the type parameter prescribed by the user (eg, Int :: Boolean :: HNil )
  • the output type – basically the prescribed HList wrapped in TypedString : eg, 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM