我正在编写内部DSL,并且正在使用Shapeless来强制执行类型安全。但是,我遇到了一个问题。
问题的简化版如下:
请考虑以下代码段:
import shapeless._
import syntax.std.function._
import ops.function._
implicit class Ops[P <: Product, L <: HList](p: P)(implicit val gen: Generic.Aux[P, L]) {
def ~|>[F, R](f: F)(implicit fp: FnToProduct.Aux[F, L ⇒ R]) =
f.toProduct(gen.to(p))
}
(1, 2) ~|> ((x: Int, y: Int) ⇒ x + y) // -> at compile time, it ensures the types are aligned.
(1, 2, 3) ~|> ((x: Int, y: Int, z: Int) ⇒ x + y + z) // -> compiles okay
(1, "2", 3) ~|> ((x: Int, y: Int, z: Int) ⇒ x + y + z) // -> gives a compile error, as expected.
但是,我想使用容器类型Place[A]
而不是A.
case class Place[A](a: A)
val a = Place[Int](1)
val b = Place[Int]("str")
并确保类型与类型参数对齐。
(a, b) ~|> ((x: Int, y: String) ⇒ x.toString + y)
也就是说,在上面的例子中,我希望根据Place [_]的类型参数来检查类型,在上面的例子中,Int
和String
分别为。 / p>
非常感谢您的帮助!
答案 0 :(得分:2)
Unwrapped
可让您为AnyVal
的每个条目提取LiftAll
,HList
传唤给定类型类的内容。如果我理解你正在尝试做什么,它可能如下所示:
case class Place[A](a: A) extends AnyVal // cuz why not?
implicit class Ops[P <: Product, L <: HList, U <: HList, C <: HList](p: P)
(implicit
val g: Generic.Aux[P, L],
val l: LiftAll.Aux[Unwrapped, L, U],
val c: Comapped.Aux[Place, L, C]
) {
def ~|>[F, R](f: F)(implicit fp: FnToProduct.Aux[F, C ⇒ R]) = {
def fuckitZip(l1: HList, l2: HList): HList = (l1, l2) match {
case (h1 :: t1, h2 :: t2) => (h1, h2) :: fuckitZip(l1, l2)
case _ => HNil
}
def fuckitMap(l: HList, f: Any => Any): HList = l match {
case HNil => HNil
case h :: t => f(h) :: fuckitMap(t, f)
}
def f(a: Any): Any = a match {
case (x: Any, y: Any) =>
x.asInstanceOf[Unwrapped[Any] { type U = Any }].unwrap(y)
}
val zp = fuckitZip(g.to(p), l.instances)
val uw = fuckitMap(zp, f _).asInstanceOf[C]
f.toProduct(uw)
}
}
请注意,我在这里使用了Comapped
和一个不安全的zip / map来保持简单,使其具有适当的HList
zip / map的类型安全作为练习。
通常使用这些复杂的转换,通过内联所有内容来重新实现具有专用类型类的所有内容可能更简单(并且将更快地运行/编译器),我只是想表明这对于基本操作是可行的:)