shapeless HList to TupleN where the tuple shape need not exactly match the HList shape

后端 未结 1 2147
青春惊慌失措
青春惊慌失措 2021-02-13 17:58

I would like to create the equivalent of:

def toTupleN[A1, ..., AN, L <: HList](l: L): TupleN[A1, ..., AN]

Code using toTupleN

相关标签:
1条回答
  • 2021-02-13 18:42

    I haven't been able to make target type inference work out quite the way you wanted, but as compensation I've generalized to an arbitrary product type via shapeless's Generic,

    import shapeless._, ops.hlist._, test._
    
    object Demo {
      trait UniqueSelect[L <: HList, M <: HList] {
        def apply(l: L): M
      }
    
      object UniqueSelect {
        implicit def hnil[L <: HList]: UniqueSelect[L, HNil] =
          new UniqueSelect[L, HNil] {
            def apply(l: L): HNil = HNil
          }
    
        implicit def hcons[L <: HList, H, T <: HList, S <: HList]
          (implicit
            pt: Partition.Aux[L, H, H :: HNil, S],
            ust: UniqueSelect[S, T]
          ): UniqueSelect[L, H :: T] =
          new UniqueSelect[L, H :: T] {
            def apply(l: L): H :: T = {
              val (h :: HNil, s) = pt(l)
              h :: ust(s)
            }
          }
      }
    
      def toProductUniquely[P <: Product] = new ToProductUniquely[P]
      class ToProductUniquely[P <: Product] {
        def apply[L <: HList, M <: HList](l: L)
          (implicit gen: Generic.Aux[P, M], up: UniqueSelect[L, M]): P =
            gen.from(up(l))
      }
    
      val l = 23 :: (1, "wibble") :: (2, "wobble") :: "foo" :: HNil
    
      val t2 = toProductUniquely[(String, Int)](l)
      typed[(String, Int)](t2)
      assert(t2 == ("foo", 23))
    
      illTyped("""
      toProductUniquely[(String, String)](l)
      """)
    
      illTyped("""
      toProductUniquely[Tuple1[(Int, String)]](l)
      """)
    }
    

    Update 1

    Adding support for the selection being satisfied by subtypes of the requested types is fairly straightforward if we say that where we have types A and B <: A then the selection of A from A :: B :: HNil is ambiguous because both elements conform to A. This can be done by adding a SubtypeUnifier to the witnesses in the previous definition of hcons,

    import shapeless._, ops.hlist._, test._
    
    object Demo extends App {
      trait UniqueSelect[L <: HList, M <: HList] {
        def apply(l: L): M
      }
    
      object UniqueSelect {
        implicit def hnil[L <: HList]: UniqueSelect[L, HNil] =
          new UniqueSelect[L, HNil] {
            def apply(l: L): HNil = HNil
          }
    
        implicit def hcons[L <: HList, M <: HList, H, T <: HList, S <: HList]
          (implicit
            su: SubtypeUnifier.Aux[L, H, M],
            pt: Partition.Aux[M, H, H :: HNil, S],
            upt: UniqueSelect[S, T]
          ): UniqueSelect[L, H :: T] =
          new UniqueSelect[L, H :: T] {
            def apply(l: L): H :: T = {
              val (h :: HNil, s) = pt(su(l))
              h :: upt(s)
            }
          }
      }
    
      def toProductUniquely[P <: Product] = new ToProductUniquely[P]
      class ToProductUniquely[P <: Product] {
        def apply[L <: HList, M <: HList](l: L)
          (implicit gen: Generic.Aux[P, M], up: UniqueSelect[L, M]): P =
            gen.from(up(l))
      }
    
      class A
      class B extends A
      class C
    
      val ac = new A :: new C :: HNil
      val bc = new B :: new C :: HNil
      val abc = new A :: new B :: new C :: HNil
    
      // Exact match
      val tac = toProductUniquely[(A, C)](ac)
      typed[(A, C)](tac)
    
      // Subtype
      val tbc = toProductUniquely[(A, C)](bc)
      typed[(A, C)](tbc)
    
      // Exact match again
      val tabc = toProductUniquely[(B, C)](abc)
      typed[(B, C)](tabc)
    
      // Ambiguous due to both elements conforming to A
      illTyped("""
      toProductUniquely[(A, C)](abc)
      """)
    }
    

    Update 2

    We can also accommodate a unification semantics which gives preference to exact match and then falls back to a unique subtype as described in your updated question. We do this by combining the instances from the two solutions above: the exact match instance from the first at normal priority and the subtype match instance at low priority,

    import shapeless._, ops.hlist._, test._
    
    object Demo extends App {
      trait UniqueSelect[L <: HList, M <: HList] {
        def apply(l: L): M
      }
    
      object UniqueSelect extends UniqueSelect0 {
        implicit def hnil[L <: HList]: UniqueSelect[L, HNil] =
          new UniqueSelect[L, HNil] {
            def apply(l: L): HNil = HNil
          }
    
        implicit def hconsExact[L <: HList, H, T <: HList, S <: HList]
          (implicit
            pt: Partition.Aux[L, H, H :: HNil, S],
            upt: UniqueSelect[S, T]
          ): UniqueSelect[L, H :: T] =
          new UniqueSelect[L, H :: T] {
            def apply(l: L): H :: T = {
              val (h :: HNil, s) = pt(l)
              h :: upt(s)
            }
          }
      }
    
      trait UniqueSelect0 {
        implicit def hconsSubtype[L <: HList, M <: HList, H, T <: HList, S <: HList]
          (implicit
            su: SubtypeUnifier.Aux[L, H, M],
            pt: Partition.Aux[M, H, H :: HNil, S],
            upt: UniqueSelect[S, T]
          ): UniqueSelect[L, H :: T] =
          new UniqueSelect[L, H :: T] {
            def apply(l: L): H :: T = {
              val (h :: HNil, s) = pt(su(l))
              h :: upt(s)
            }
          }
      }
    
      def toProductUniquely[P <: Product] = new ToProductUniquely[P]
      class ToProductUniquely[P <: Product] {
        def apply[L <: HList, M <: HList](l: L)
          (implicit gen: Generic.Aux[P, M], up: UniqueSelect[L, M]): P = gen.from(up(l))
      }
    
      trait A
      trait B extends A
      trait C extends A
    
      val a: A = new A {}
      val b: B = new B {}
      val c: C = new C {}
    
      // (b, 5): subtypes match supertypes when there is no exact match
      toProductUniquely[(A, Int)](5 :: b :: HNil)
    
      // (a, 5): only one exact match is available
      toProductUniquely[(A, Int)](5 :: b :: a :: HNil)
    
      // compile error: more than one exact match is available
      illTyped("""
      toProductUniquely[(A, Int)](5 :: a :: a :: HNil)
      """)
    
      // compile error: more than one inexact match is available
      illTyped("""
      toProductUniquely[(A, Int)](5 :: b :: c :: HNil)
      """)
    }
    
    0 讨论(0)
提交回复
热议问题