0

我正在尝试使用 shapeless 的 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
4

1 回答 1

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),所以它不能从我的类中可用的内容构建。

一些解决方法:

  • 在 HList 旁边手动构建 List

    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
    
于 2017-03-29T22:39:27.090 回答