我正在尝试编写一个函数,该函数使用Shapeless将给定的任意case类用给定的新值替换第一个字段的值。
到目前为止,我有以下内容:
def replaceHead[T, H, R >: H :: HList](obj: T, newHead: H)(implicit generic: Generic.Aux[T, R]): T =
generic.to(obj) match {
case h :: t => generic.from(newHead :: t)
}
如果我不对R
进行任何类型限制,则不能使用generic.from(newHead :: t)
,因为可以理解,它需要R
的子类型。如果输入R >: H :: HList
,则会出现以下错误:
Error:(19, 22) could not find implicit value for parameter generic: shapeless.Generic.Aux[Test.Record,String :: shapeless.HList]
println(replaceHead(Record("abc", 123), "def"))
我要使其“工作”的唯一方法是使用这种类型安全的丑陋骇客:
def replaceHead[T, H, R](obj: T, newHead: H)(implicit generic: Generic.Aux[T, R]): T =
generic.to(obj) match {
case h :: t => generic.from((newHead :: t).asInstanceOf[R])
}
replaceHead(Record("abc", 123), "def") // Works
replaceHead(Record("abc", 123), 456) // Crashes at runtime
我知道根本的问题是因为R
最终是例如String :: Int :: HNil
,而是 String :: HList
但String :: HList
不一定{ {1}},但是在不删除尾部的类型签名的情况下,我无法找到一种访问通用表示形式的头的方法。
答案 0 :(得分:1)
以下应该可以解决问题,
def replaceHead[C, R <: HList, H, T <: HList](c: C, newHead: H)
(implicit
gen: Generic.Aux[C, R],
ev1: (H :: T) =:= R,
ev2: R =:= (H :: T)
): C = gen.from(newHead :: gen.to(c).tail)
scala> replaceHead(Foo(23, "foo"), 13)
res0: Foo = Foo(13,foo)
我们需要两个类型相等性,以证明表示类型R
在两个方向上都与H :: T
相同。
答案 1 :(得分:0)
这是一个很好的问题,我不敢相信已经找到了最简单的解决方案。但这确实可行:
trait NonEmptyHList[T, Head] {
def replaceHead(t: T, h: Head): T
}
implicit def nonEmptyHList[H, Rest <: HList]: NonEmptyHList[H :: Rest, H] = new NonEmptyHList[H :: Rest, H] {
def replaceHead(t: H :: Rest, h: H): H :: Rest = t match {
case _ :: rest => h :: rest
}
}
def replaceHead[T, H, R](obj: T, h: H)(implicit generic: Generic.Aux[T, R], nonEmpty: NonEmptyHList[R, H]): T = {
generic.from(nonEmpty.replaceHead(generic.to(obj), h))
}
val rec = new Record("hello", 1)
replaceHead(rec, "hi") // Record("hi", 1)
replaceHead(rec, 2) // compile error