Scala 无形:从 Mapper 派生类型
Scala shapeless: derive type from Mapper
方法 doesNotCompile
只接受仅包含 Label[A]
条目的 HList。有一个 Mapper 将 Label[A] 转换为 String(准确地说:Const[String]#λ
)。
但是,当我应用映射器时,return 类型是 ev1.Out。我知道这实际上是一个仅包含字符串的 HList,但我如何才能说服编译器?
import shapeless._
import shapeless.poly._
import shapeless.ops.hlist._
import shapeless.UnaryTCConstraint._
object Util {
case class Label[A](name: String, value: A)
object GetLabelName extends (Label ~> Const[String]#λ) {
def apply[A](label: Label[A]) = label.name
}
}
object Main {
import Util._
def bar(l: List[String]) = ???
def compiles = {
val names = "a" :: "b" :: HNil
bar(names.toList)
}
// A is an HList whose members are all Label[_]
def doesNotCompile[A <: HList : *->*[Label]#λ](labels: A)(
implicit ev1: Mapper[GetLabelName.type, A]) = {
// implicit ev1: Mapper[String, A]) = {
val names = labels map GetLabelName
// names is of type `ev1.Out` - I want it to be an HList of Strings
bar(names.toList)
// error: could not find implicit value for parameter toTraversableAux:
// shapeless.ops.hlist.ToTraversable.Aux[ev1.Out,List,Lub]
}
}
这是完整的要点,包括。 build.sbt - 下载并 运行 sbt compile
: https://gist.github.com/mpollmeier/6c53e375d88a32016250
如错误所述,您缺少 ToTraversable
的类型 class 实例,没有它您无法调用 names.toList
。我们可以通过添加一个隐式参数来解决这个问题,但首先我们需要解决 GetLabelName
的问题,因为在 HList
上映射时不能使用它:
scala> GetLabelName(Label("a", 5))
res3: String = a
scala> (Label("a", 5) :: HNil) map GetLabelName
java.lang.AbstractMethodError: GetLabelName$.caseUniv()Lshapeless/PolyDefns$Case;
... 43 elided
一个解决方案是创建一个新的多态函数扩展 Poly1
:
object getLabelName extends Poly1 {
implicit def caseLabel[T] = at[Label[T]](_.name)
}
现在我们可以将隐式参数添加到函数中:
def bar(l: List[String]) = l
def labelNames[L <: HList : *->*[Label]#λ, M <: HList](labels: L)(implicit
mapper: Mapper.Aux[getLabelName.type, L, M],
trav: ToTraversable.Aux[M, List, String]
): List[String] = bar(labels.map(getLabelName).toList)
可用作:
val labels = Label("a", 5) :: Label("b", 1d) :: Label("c", None) :: HNil
labelNames(labels) // List[String] = List(a, b, c)
方法 doesNotCompile
只接受仅包含 Label[A]
条目的 HList。有一个 Mapper 将 Label[A] 转换为 String(准确地说:Const[String]#λ
)。
但是,当我应用映射器时,return 类型是 ev1.Out。我知道这实际上是一个仅包含字符串的 HList,但我如何才能说服编译器?
import shapeless._
import shapeless.poly._
import shapeless.ops.hlist._
import shapeless.UnaryTCConstraint._
object Util {
case class Label[A](name: String, value: A)
object GetLabelName extends (Label ~> Const[String]#λ) {
def apply[A](label: Label[A]) = label.name
}
}
object Main {
import Util._
def bar(l: List[String]) = ???
def compiles = {
val names = "a" :: "b" :: HNil
bar(names.toList)
}
// A is an HList whose members are all Label[_]
def doesNotCompile[A <: HList : *->*[Label]#λ](labels: A)(
implicit ev1: Mapper[GetLabelName.type, A]) = {
// implicit ev1: Mapper[String, A]) = {
val names = labels map GetLabelName
// names is of type `ev1.Out` - I want it to be an HList of Strings
bar(names.toList)
// error: could not find implicit value for parameter toTraversableAux:
// shapeless.ops.hlist.ToTraversable.Aux[ev1.Out,List,Lub]
}
}
这是完整的要点,包括。 build.sbt - 下载并 运行 sbt compile
: https://gist.github.com/mpollmeier/6c53e375d88a32016250
如错误所述,您缺少 ToTraversable
的类型 class 实例,没有它您无法调用 names.toList
。我们可以通过添加一个隐式参数来解决这个问题,但首先我们需要解决 GetLabelName
的问题,因为在 HList
上映射时不能使用它:
scala> GetLabelName(Label("a", 5))
res3: String = a
scala> (Label("a", 5) :: HNil) map GetLabelName
java.lang.AbstractMethodError: GetLabelName$.caseUniv()Lshapeless/PolyDefns$Case;
... 43 elided
一个解决方案是创建一个新的多态函数扩展 Poly1
:
object getLabelName extends Poly1 {
implicit def caseLabel[T] = at[Label[T]](_.name)
}
现在我们可以将隐式参数添加到函数中:
def bar(l: List[String]) = l
def labelNames[L <: HList : *->*[Label]#λ, M <: HList](labels: L)(implicit
mapper: Mapper.Aux[getLabelName.type, L, M],
trav: ToTraversable.Aux[M, List, String]
): List[String] = bar(labels.map(getLabelName).toList)
可用作:
val labels = Label("a", 5) :: Label("b", 1d) :: Label("c", None) :: HNil
labelNames(labels) // List[String] = List(a, b, c)