捕获hlist的ToList证据作为隐式类参数

时间:2017-03-28 07:40:10

标签: scala shapeless hlist

我试图使用无形的hlist来构建内省的URL模板,但是我在浏览HList时遇到了麻烦。以下不编译:

import shapeless.{::, HList, HNil}
import shapeless.LUBConstraint._
import shapeless.ops.hlist.ToTraversable._

import scala.util.Try
import shapeless.ops.hlist._

object Path {
  def /(s: String) = Path(PathLiteral(s) :: HNil)

  def param[T](name: String) = PathParameter(name)
}


sealed trait PathSegment[+T]
case class PathLiteral(value: String) extends PathSegment[String]
case class PathParameter[+T](name: String) extends PathSegment[T]


case class Path[L <: HList : <<:[PathSegment[_]]#λ](segments: L)
                                                   (implicit ev: ToList[L, PathSegment[_]])
{
  def /(literal: String) = Path(PathLiteral(literal) :: segments)

  def /[T](param: PathParameter[T]) = Path(param :: segments)

  override def toString: String = s"Path(${segments.toList.reverse})"
}

object Test extends App {

  import Path.param
  val myPath = Path / "v1" / "pets" / param[String]("name") / "pictures"
  println(myPath)

}

在我看来,ToTraversable._导入涵盖了HNil案例以及具有HList尾部和具有相同最小上界的新头部的情况。显然,我要么错过了导入,要么误解了一切。

我不确定是否将类中的证据缓存为隐式参数是犹太教的;我这样做是因为

  1. 我不希望hlist详细信息泄漏到外部API
  2. 我需要一个很好的toString

1 个答案:

答案 0 :(得分:0)

这不起作用的原因是隐含的,可以证明:

给出

ToList[PathLiteral :: L, PathSegment[_]]

  • ToList[L, PathSegment[L]]
  • L <: HList

是来自hlists.scala的隐式def的类型签名:

implicit def hlistToTraversable[H1, H2, T <: HList, LubT, Lub0, M[_]]
  (implicit
    tttvs  : Aux[H2 :: T, M, LubT],
    u      : Lub[H1, LubT, Lub0],
    cbf    : CanBuildFrom[M[Lub0], Lub0, M[Lub0]]) : Aux[H1 :: H2 :: T, M, Lub0]

实际上需要知道列表头部的类型以及新项目(H1H2),因此无法根据我班级内的可用内容构建。< / p>

一些解决方法:

  • 在HList旁边手动构建列表

    case class Path[L <: HList](segments: L, segmentsList: List[PathSegment[_]]) {
      def /(literal: String) = Path(PathLiteral(literal) :: segments, PathLiteral(literal) :: segmentsList)
      def /[T](param: PathParameter[T]) = Path(param :: segments, param :: segmentsList)
      override def toString: String = s"Path(${segmentsList.reverse})"
    }
    
  • 将隐式参数推送到Path的方法

    def printPath[L <: HList](path: Path[L])
                             (implicit ev: ToList[L, PathSegment[_]]) =
    path.segments.toList.reverse.collect {
      case PathLiteral(value) => URLEncoder.encode(value, "UTF-8")
      case PathParameter(name) => s"<$name>"
    }.mkString("/")
    
    printPath(Path / "v1" / "pets"  / param[String]("name") / "pictures")
    // v1/pets/<name>/pictures