强迫无形记录的价值

Coercing the value of a shapeless record

我有一张无形唱片的包装纸。

我想从该记录中提取一个值,并证明它是一个多态类型的实例,例如List[_]

import shapeless._
import shapeless.record._
import shapeless.ops.record._
import shapeless.syntax.singleton._

case class All[L <: HList](containers: L) {


  def getValue[Value, A](containerKey: Witness)
                        (implicit sel: Selector.Aux[L, containerKey.T, Value],
                         equiv: Value =:= List[A]
                        ): List[A] =
    equiv.apply(containers.get(containerKey))

}

现在,如果我明确指定类型参数 ValueA,我可以调用 getValue,但是因为我使用的类型比 [=16] 复杂得多=], 我真的需要推断这些类型参数。

val all = All(
  'x ->> List[Int](1, 2, 3) ::
  'y ->> List[String]("a", "b") ::
  'z ->> 90
  HNil
)

// doesn't compile: Cannot prove that Value =:= List[A].
all.getValue('x)

// compiles
all.getValue[List[Int], Int]('x)

有没有办法提取一个值,将其强制转换为例如List[_],而不必指定任何类型参数?

请注意,如果我想证明值是一个简单的单态类型,例如Value =:= Int,只是不适合 Value =:= List[A]

至少有两种方法可以做到,并且都涉及更改 getValue 的签名。

首先,您可以只使用一个受约束的泛型参数:

  def getValue[R <: List[_]](containerKey: Witness)
                        (implicit sel: Selector.Aux[L, containerKey.T, R]): R =
    containers.get(containerKey)

请注意,因为我们使用 R 作为 return 类型,所以有关结果的编译时信息不会丢失。如果 containerKey 中的值不是列表,您将无法调用此函数。


其次,您可以使用子类型边界。我不知道它为什么有效,真的。我怀疑使用过于严格的约束会导致编译器忽略一些使用细化类型的解决方案。如果您将 Selector 中的类型参数替换为有界通配符,这两种方法都有效:

  def getValue[A](containerKey: Witness)
                        (implicit sel: Selector.Aux[L, containerKey.T, _ <: List[A]]): List[A] =
    containers.get(containerKey)

或者如果您使用子类型证据 <:< 而不是等式 =:=:

  def getValue[Value, A](containerKey: Witness)
                        (implicit sel: Selector.Aux[L, containerKey.T, Value],
                         equiv: Value <:< List[A]
                        ): List[A] =
    equiv.apply(containers.get(containerKey))