【问题标题】:shapeless HList to TupleN where the tuple shape need not exactly match the HList shapeshapeless HList 到 TupleN,其中元组形状不需要完全匹配 HList 形状
【发布时间】:2016-04-23 23:58:05
【问题描述】:

我想创建等价于:

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

使用toTupleN 的代码只有在l 中只有一个N 值组合可以用来创建元组时才能编译。其他任何事情都应该产生编译时错误。应考虑可用的隐式转换。请注意,l 的大小或其中的值的顺序没有限制。

示例:

val l = 23 :: (1, "wibble") :: (2, "wobble") :: "foo" :: HNil
// l: shapeless.::[Int,shapeless.::[(Int, String),shapeless.::[(Int, String),shapeless.::[String,shapeless.HNil]]]] = 23 :: (1,wibble) :: (2,wobble) :: foo :: HNil

val t2: (String, Int) = toTuple2(l)
// t2: (String, Int) = (foo,23)

val nope: (String, String) = toTuple2(l)
// Compiler error because no combination of l's values can create nope

val nein: ((Int, String)) = toTuple2(l)
// Another compiler error because there is more than one way l's values can create nein

这个问题来自answer 到下面的question。此问题中更通用的机制可用于创建数据结构并使用 FunctionN#tupled 调用任何标准函数(其参数属于不同类型)。

更新:

使用子类型定义所需行为的一些示例:

trait A
trait B extends A
trait C extends A

val a: A
val b: B
val c: C

toTuple2[(A, Int)](5 :: b :: HNil)      // (b, 5): subtypes match supertypes when there is no exact match
toTuple2[(A, Int)](5 :: b :: a :: HNil) // (a, 5): only one exact match is available
toTuple2[(A, Int)](5 :: a :: a :: HNil) // compile error: more than one exact match is available
toTuple2[(A, Int)](5 :: b :: c :: HNil) // compile error: more than one inexact match is available

【问题讨论】:

    标签: scala tuples shapeless hlist


    【解决方案1】:

    我无法按照您想要的方式进行目标类型推断,但作为补偿,我已通过 shapeless 的 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)
      """)
    }
    

    更新 1

    如果我们说我们有AB &lt;: A 类型,那么添加对由所请求类型的子类型满足的选择的支持是相当简单的,那么从A :: B :: HNil 中选择A 是不明确的,因为这两个元素符合A。这可以通过在hcons 的先前定义中的见证人中添加SubtypeUnifier 来完成,

    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)
      """)
    }
    

    更新 2

    我们还可以采用统一语义,该语义优先考虑精确匹配,然后回退到您更新的问题中描述的唯一子类型。我们通过组合上述两种解决方案中的实例来做到这一点:第一个的完全匹配实例具有正常优先级,而子类型匹配实例具有低优先级,

    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)
      """)
    }
    

    【讨论】:

    • 这很有趣,迈尔斯。目前,我坚持使用 shapeless 2.0.0(部署到不允许编译器插件的托管 2.10.5 环境),所以我错过了 ops.hlist 的一些新增功能,例如 Partition。拉入Partition 代码以运行您的示例,我看到了对toTuple2 的依赖,我在代码库中找不到它。请给我指出正确的方向好吗?
    • 它是here
    • 这只适用于完全匹配的类型。例如,5 :: Seq[Int](1,2,3) :: HNil 不能转换为 (Int, Seq[Any]),即使 Seq[Int] &lt;: Seq[Any]。有没有办法解决这个问题?
    • 给定类型AB &lt;: A,如果我们从A :: B :: HNil 中选择A,您是否期望结果为:1) A(完全匹配)2) @987654344 @(更精确)或 3)非编译,因为两个元素都符合 A,所以含糊不清?如果您可以添加一个示例来说明您对问题的选择,那将是有用的。
    • Miles,我添加了一个示例,其中包含您的问题的期望行为(期望的结果是选项 1)以及可能出现的其他情况,例如,不止一个完全匹配和不止一个不精确匹配。这是否充分定义了问题?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多