将无形的HList转换为TupleN,其中元组的形状不一定要完全匹配HList的形状。

13

我想创建以下相当的内容:

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

这个问题源于对以下问题答案。这个问题中更通用的机制可以用于创建数据结构并使用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
1个回答

3

虽然我无法完全按照您的意愿进行目标类型推断,但为了弥补这一点,我通过使用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 <: 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)
  """)
}

这非常有趣,Miles。目前,我被困在shapeless 2.0.0中(部署到不允许编译器插件的托管2.10.5环境中),因此我错过了一些对ops.hlist的添加,例如Partition。将Partition代码拉入以运行您的示例,我发现依赖于toTuple2,但我在代码库中找不到它。您能指点我正确的方向吗? - Sim
它在这里:[https://github.com/milessabin/shapeless/blob/master/core/src/main/scala/shapeless/ops/hlists.scala#L2618]。 - Miles Sabin
这仅适用于精确类型匹配。例如,5 :: Seq[Int](1,2,3) :: HNil 不能转换为 (Int, Seq[Any]),即使 Seq[Int] <: Seq[Any]。有没有一种方法来解决这个问题? - Sim
给定类型 AB <: A,如果我们从 A :: B :: HNil 中选择 A,你认为结果会是:1)A(精确匹配)2)B(更精确)还是3)不编译,因为两个元素都符合 A,所以模糊不清?如果您能够在问题中添加一个说明您选择的示例,那将非常有用。 - Miles Sabin
Miles,我已经添加了一个示例,涵盖了你的问题特别是所需的行为(期望结果是选项1)以及可能出现的其他情况,例如,多个精确匹配和多个不精确匹配。这定义了问题足够吗? - Sim

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接