虽然我无法完全按照您的意愿进行目标类型推断,但为了弥补这一点,我通过使用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
如果我们说在类型A
和B <: 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
val tac = toProductUniquely[(A, C)](ac)
typed[(A, C)](tac)
val tbc = toProductUniquely[(A, C)](bc)
typed[(A, C)](tbc)
val tabc = toProductUniquely[(B, C)](abc)
typed[(B, C)](tabc)
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 {}
toProductUniquely[(A, Int)](5 :: b :: HNil)
toProductUniquely[(A, Int)](5 :: b :: a :: HNil)
illTyped("""
toProductUniquely[(A, Int)](5 :: a :: a :: HNil)
""")
illTyped("""
toProductUniquely[(A, Int)](5 :: b :: c :: HNil)
""")
}
ops.hlist
的添加,例如Partition。将Partition
代码拉入以运行您的示例,我发现依赖于toTuple2
,但我在代码库中找不到它。您能指点我正确的方向吗? - Sim5 :: Seq[Int](1,2,3) :: HNil
不能转换为(Int, Seq[Any])
,即使Seq[Int] <: Seq[Any]
。有没有一种方法来解决这个问题? - SimA
和B <: A
,如果我们从A :: B :: HNil
中选择A
,你认为结果会是:1)A
(精确匹配)2)B
(更精确)还是3)不编译,因为两个元素都符合A
,所以模糊不清?如果您能够在问题中添加一个说明您选择的示例,那将非常有用。 - Miles Sabin