折页折返类型无法正确推断

时间:2019-01-18 23:27:42

标签: scala shapeless

我正在尝试创建一个可折叠成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)]

2 个答案:

答案 0 :(得分:1)

也许对无形变形有更好的了解的人可以为您提供更好的答案。根据我的理解,问题出在类型推断步骤。如果您明确指定所有类型,如

val result: Foo[(Int, String)] = extract[(Foo[Int], Foo[String]),
    Foo[Int] :: Foo[String] :: HNil,
    Int :: String :: HNil]((Foo(1), Foo("a")))

代码正确地进行类型检查。显然,您并不想明确指定这些类型。

根据我的理解,编译器无法推断出良好的Btupler.Out,因为它们与InA的耦合不够紧密。解决此问题的一种方法是引入类似这样的中间特征:

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))
  }
}