我正在尝试创建一个可折叠成Foo
s元组的多边形函数:
case class Foo[A](a: A)
object extractFold extends Poly2 {
implicit def default[A, As <: HList]: Case.Aux[Foo[A], Foo[As], Foo[A :: As]] = {
???
}
}
def extract[In, A <: HList, B <: HList](keys: In)
(implicit
gen: Generic.Aux[In, A],
folder: RightFolder.Aux[A, Foo[HNil], extractFold.type, Foo[B]],
tupler: Tupler[B])
: Foo[tupler.Out] = {
???
}
val result = extract((Foo(1), Foo("a")))
该函数在运行时有效,但是编译器推断的结果类型始终为Foo[Unit]
,这是不正确的-在此示例中,它应该为Foo[(Int, String)]
答案 0 :(得分:1)
也许对无形变形有更好的了解的人可以为您提供更好的答案。根据我的理解,问题出在类型推断步骤。如果您明确指定所有类型,如
val result: Foo[(Int, String)] = extract[(Foo[Int], Foo[String]),
Foo[Int] :: Foo[String] :: HNil,
Int :: String :: HNil]((Foo(1), Foo("a")))
代码正确地进行类型检查。显然,您并不想明确指定这些类型。
根据我的理解,编译器无法推断出良好的B
和tupler.Out
,因为它们与In
和A
的耦合不够紧密。解决此问题的一种方法是引入类似这样的中间特征:
trait Extractor[L <: HList, HF] {
type FR <: HList
type TR
val folder: RightFolder.Aux[L, Foo[HNil], HF, Foo[FR]]
val tupler: Tupler.Aux[FR, TR]
}
object Extractor {
type Aux[L <: HList, HF, FR0 <: HList, TR0] = Extractor[L, HF] {type FR = FR0; type TR = TR0}
implicit def wrap[L <: HList, In, HF, FR0 <: HList, TR0](implicit folder0: RightFolder.Aux[L, Foo[HNil], HF, Foo[FR0]],
tupler0: Tupler.Aux[FR0, TR0]) = new Extractor[L, HF] {
type FR = FR0
type TR = TR0
override val folder = folder0
override val tupler = tupler0
}
}
然后像这样使用它:
def extract[In, A <: HList, B <: HList, C](keys: In)
(implicit gen: Generic.Aux[In, A],
extractor: Extractor.Aux[A, extractFold.type, B, C])
: Foo[C] = {
val hli = gen.to(keys)
val fr = extractor.folder(hli, Foo(HNil))
Foo(extractor.tupler(fr.a))
}
这是一个骇人听闻的解决方案,但至少它似乎可以正常工作(另请参见online demo)。
答案 1 :(得分:1)
你为什么这么认为
编译器推断的结果类型始终为Foo [Unit]
?
以下代码
import shapeless.ops.hlist.{RightFolder, Tupler}
import shapeless.{::, Generic, HList, HNil, Poly2}
import scala.reflect.runtime.universe.{typeOf, Type, TypeTag}
object App {
def getType[T: TypeTag](t: T): Type = typeOf[T]
case class Foo[A](a: A)
object extractFold extends Poly2 {
implicit def default[A, As <: HList]: Case.Aux[Foo[A], Foo[As], Foo[A :: As]] =
at { case (Foo(a), Foo(as)) => Foo(a :: as) }
}
def extract[In, A <: HList, B <: HList](keys: In)(implicit
gen: Generic.Aux[In, A],
folder: RightFolder.Aux[A, Foo[HNil], extractFold.type, Foo[B]],
tupler: Tupler[B]
): Foo[tupler.Out] =
Foo(tupler(folder(gen.to(keys), Foo(HNil)).a))
val result = extract((Foo(1), Foo("a")))
def main(args: Array[String]): Unit = {
println(
getType(result)
)
}
}
打印
App.Foo[(Int, java.lang.String)]
此外,如果您更改了行
val result: Foo[Unit] = extract((Foo(1), Foo("a")))
代码无法编译。
顺便说一句,可以使用
import shapeless.PolyDefns.~>
import shapeless.ops.hlist.{Comapped, NatTRel, Tupler}
import shapeless.{Generic, HList, Id}
object App {
case class Foo[A](a: A)
def extract[In, A <: HList, B <: HList](keys: In)(implicit
gen: Generic.Aux[In, A],
comapped: Comapped.Aux[A, Foo, B],
natTRel: NatTRel[A, Foo, B, Id],
tupler: Tupler[B]
): Foo[tupler.Out] =
Foo(tupler(natTRel.map(new (Foo ~> Id) { def apply[T](foo: Foo[T]) = foo.a }, gen.to(keys))))
val result = extract((Foo(1), Foo("a")))
def main(args: Array[String]): Unit = {
println(result)//Foo((1,a))
}
}